From d0a60829797e4c9c3b2214f493b0f47dbc0dca3b Mon Sep 17 00:00:00 2001 From: jdecroock Date: Fri, 4 Oct 2024 12:31:35 +0200 Subject: [PATCH 1/4] Allow skipping suggestions --- src/execution/collectFields.ts | 24 +++++++- src/execution/execute.ts | 46 ++++++++++++--- src/execution/values.ts | 28 ++++++++- src/type/definition.ts | 26 ++++++--- .../__tests__/coerceInputValue-test.ts | 14 +++-- src/utilities/coerceInputValue.ts | 58 ++++++++++++++++--- src/validation/ValidationContext.ts | 11 ++++ .../rules/FieldsOnCorrectTypeRule.ts | 10 +++- .../rules/KnownArgumentNamesRule.ts | 24 ++++---- src/validation/rules/KnownTypeNamesRule.ts | 10 ++-- .../rules/PossibleTypeExtensionsRule.ts | 4 +- .../rules/ValuesOfCorrectTypeRule.ts | 14 +++-- src/validation/validate.ts | 4 +- 13 files changed, 218 insertions(+), 55 deletions(-) diff --git a/src/execution/collectFields.ts b/src/execution/collectFields.ts index bd653e46943..a377edc0684 100644 --- a/src/execution/collectFields.ts +++ b/src/execution/collectFields.ts @@ -55,6 +55,7 @@ interface CollectFieldsContext { operation: OperationDefinitionNode; runtimeType: GraphQLObjectType; visitedFragmentNames: Set; + shouldProvideSuggestions: boolean; } /** @@ -66,12 +67,14 @@ interface CollectFieldsContext { * * @internal */ +// eslint-disable-next-line @typescript-eslint/max-params export function collectFields( schema: GraphQLSchema, fragments: ObjMap, variableValues: VariableValues, runtimeType: GraphQLObjectType, operation: OperationDefinitionNode, + shouldProvideSuggestions: boolean, ): { groupedFieldSet: GroupedFieldSet; newDeferUsages: ReadonlyArray; @@ -85,6 +88,7 @@ export function collectFields( runtimeType, operation, visitedFragmentNames: new Set(), + shouldProvideSuggestions, }; collectFieldsImpl( @@ -114,6 +118,7 @@ export function collectSubfields( operation: OperationDefinitionNode, returnType: GraphQLObjectType, fieldDetailsList: FieldDetailsList, + shouldProvideSuggestions: boolean, ): { groupedFieldSet: GroupedFieldSet; newDeferUsages: ReadonlyArray; @@ -125,6 +130,7 @@ export function collectSubfields( runtimeType: returnType, operation, visitedFragmentNames: new Set(), + shouldProvideSuggestions, }; const subGroupedFieldSet = new AccumulatorMap(); const newDeferUsages: Array = []; @@ -172,7 +178,12 @@ function collectFieldsImpl( switch (selection.kind) { case Kind.FIELD: { if ( - !shouldIncludeNode(selection, variableValues, fragmentVariableValues) + !shouldIncludeNode( + selection, + variableValues, + fragmentVariableValues, + context.shouldProvideSuggestions, + ) ) { continue; } @@ -189,6 +200,7 @@ function collectFieldsImpl( selection, variableValues, fragmentVariableValues, + context.shouldProvideSuggestions, ) || !doesFragmentConditionMatch(schema, selection, runtimeType) ) { @@ -201,6 +213,7 @@ function collectFieldsImpl( fragmentVariableValues, selection, deferUsage, + context.shouldProvideSuggestions, ); if (!newDeferUsage) { @@ -235,6 +248,7 @@ function collectFieldsImpl( fragmentVariableValues, selection, deferUsage, + context.shouldProvideSuggestions, ); if ( @@ -244,6 +258,7 @@ function collectFieldsImpl( selection, variableValues, fragmentVariableValues, + context.shouldProvideSuggestions, )) ) { continue; @@ -264,6 +279,7 @@ function collectFieldsImpl( selection, fragmentVariableSignatures, variableValues, + context.shouldProvideSuggestions, fragmentVariableValues, ); } @@ -300,16 +316,19 @@ function collectFieldsImpl( * deferred based on the experimental flag, defer directive present and * not disabled by the "if" argument. */ +// eslint-disable-next-line @typescript-eslint/max-params function getDeferUsage( operation: OperationDefinitionNode, variableValues: VariableValues, fragmentVariableValues: VariableValues | undefined, node: FragmentSpreadNode | InlineFragmentNode, parentDeferUsage: DeferUsage | undefined, + shouldProvideSuggestions: boolean, ): DeferUsage | undefined { const defer = getDirectiveValues( GraphQLDeferDirective, node, + shouldProvideSuggestions, variableValues, fragmentVariableValues, ); @@ -341,10 +360,12 @@ function shouldIncludeNode( node: FragmentSpreadNode | FieldNode | InlineFragmentNode, variableValues: VariableValues, fragmentVariableValues: VariableValues | undefined, + shouldProvideSuggestions: boolean, ): boolean { const skip = getDirectiveValues( GraphQLSkipDirective, node, + shouldProvideSuggestions, variableValues, fragmentVariableValues, ); @@ -355,6 +376,7 @@ function shouldIncludeNode( const include = getDirectiveValues( GraphQLIncludeDirective, node, + shouldProvideSuggestions, variableValues, fragmentVariableValues, ); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index af4e6b99286..0c884fd8e5c 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -98,8 +98,13 @@ const collectSubfields = memoize3( returnType: GraphQLObjectType, fieldDetailsList: FieldDetailsList, ) => { - const { schema, fragments, operation, variableValues } = - validatedExecutionArgs; + const { + schema, + fragments, + operation, + variableValues, + shouldProvideSuggestions, + } = validatedExecutionArgs; return _collectSubfields( schema, fragments, @@ -107,6 +112,7 @@ const collectSubfields = memoize3( operation, returnType, fieldDetailsList, + shouldProvideSuggestions, ); }, ); @@ -155,6 +161,7 @@ export interface ValidatedExecutionArgs { validatedExecutionArgs: ValidatedExecutionArgs, ) => PromiseOrValue; enableEarlyExecution: boolean; + shouldProvideSuggestions: boolean; } export interface ExecutionContext { @@ -184,6 +191,7 @@ export interface ExecutionArgs { ) => PromiseOrValue >; enableEarlyExecution?: Maybe; + shouldProvideSuggestions?: Maybe; } export interface StreamUsage { @@ -308,8 +316,14 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent( cancellableStreams: undefined, }; try { - const { schema, fragments, rootValue, operation, variableValues } = - validatedExecutionArgs; + const { + schema, + fragments, + rootValue, + operation, + variableValues, + shouldProvideSuggestions, + } = validatedExecutionArgs; const rootType = schema.getRootType(operation.operation); if (rootType == null) { throw new GraphQLError( @@ -324,6 +338,7 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent( variableValues, rootType, operation, + shouldProvideSuggestions, ); const { groupedFieldSet, newDeferUsages } = collectedFields; @@ -501,6 +516,7 @@ export function validateExecutionArgs( subscribeFieldResolver, perEventExecutor, enableEarlyExecution, + shouldProvideSuggestions, } = args; // If the schema used for execution is invalid, throw an error. @@ -559,7 +575,10 @@ export function validateExecutionArgs( schema, variableDefinitions, rawVariableValues ?? {}, - { maxErrors: 50 }, + { + maxErrors: 50, + shouldProvideSuggestions: shouldProvideSuggestions ?? true, + }, ); if (variableValuesOrErrors.errors) { @@ -579,6 +598,7 @@ export function validateExecutionArgs( subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver, perEventExecutor: perEventExecutor ?? executeSubscriptionEvent, enableEarlyExecution: enableEarlyExecution === true, + shouldProvideSuggestions: shouldProvideSuggestions ?? true, }; } @@ -762,7 +782,7 @@ function executeField( deferMap: ReadonlyMap | undefined, ): PromiseOrValue> | undefined { const validatedExecutionArgs = exeContext.validatedExecutionArgs; - const { schema, contextValue, variableValues } = validatedExecutionArgs; + const { schema, contextValue, variableValues, shouldProvideSuggestions } = validatedExecutionArgs; const fieldName = fieldDetailsList[0].node.name.value; const fieldDef = schema.getField(parentType, fieldName); if (!fieldDef) { @@ -789,6 +809,7 @@ function executeField( fieldDetailsList[0].node, fieldDef.args, variableValues, + shouldProvideSuggestions, fieldDetailsList[0].fragmentVariableValues, ); @@ -1093,12 +1114,14 @@ function getStreamUsage( ._streamUsage; } - const { operation, variableValues } = validatedExecutionArgs; + const { operation, variableValues, shouldProvideSuggestions } = + validatedExecutionArgs; // validation only allows equivalent streams on multiple fields, so it is // safe to only check the first fieldNode for the stream directive const stream = getDirectiveValues( GraphQLStreamDirective, fieldDetailsList[0].node, + shouldProvideSuggestions, variableValues, fieldDetailsList[0].fragmentVariableValues, ); @@ -2065,6 +2088,7 @@ function executeSubscription( contextValue, operation, variableValues, + shouldProvideSuggestions, } = validatedExecutionArgs; const rootType = schema.getSubscriptionType(); @@ -2081,6 +2105,7 @@ function executeSubscription( variableValues, rootType, operation, + shouldProvideSuggestions, ); const firstRootField = groupedFieldSet.entries().next().value as [ @@ -2114,7 +2139,12 @@ function executeSubscription( // Build a JS object of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. - const args = getArgumentValues(fieldDef, fieldNodes[0], variableValues); + const args = getArgumentValues( + fieldDef, + fieldNodes[0], + validatedExecutionArgs.shouldProvideSuggestions, + variableValues, + ); // Call the `subscribe()` resolver or the default resolver to produce an // AsyncIterable yielding raw payloads. diff --git a/src/execution/values.ts b/src/execution/values.ts index cce0d1d12fb..d2cdc8bfdc5 100644 --- a/src/execution/values.ts +++ b/src/execution/values.ts @@ -55,10 +55,11 @@ export function getVariableValues( schema: GraphQLSchema, varDefNodes: ReadonlyArray, inputs: { readonly [variable: string]: unknown }, - options?: { maxErrors?: number }, + options?: { maxErrors?: number; shouldProvideSuggestions?: boolean }, ): VariableValuesOrErrors { const errors: Array = []; const maxErrors = options?.maxErrors; + const shouldProvideSuggestions = options?.shouldProvideSuggestions ?? true; try { const variableValues = coerceVariableValues( schema, @@ -72,6 +73,7 @@ export function getVariableValues( } errors.push(error); }, + shouldProvideSuggestions, ); if (errors.length === 0) { @@ -89,6 +91,7 @@ function coerceVariableValues( varDefNodes: ReadonlyArray, inputs: { readonly [variable: string]: unknown }, onError: (error: GraphQLError) => void, + shouldProvideSuggestions: boolean, ): VariableValues { const sources: ObjMap = Object.create(null); const coerced: ObjMap = Object.create(null); @@ -105,7 +108,11 @@ function coerceVariableValues( const defaultValue = varSignature.defaultValue; if (defaultValue) { sources[varName] = { signature: varSignature }; - coerced[varName] = coerceDefaultValue(defaultValue, varType); + coerced[varName] = coerceDefaultValue( + defaultValue, + varType, + shouldProvideSuggestions, + ); } else if (isNonNullType(varType)) { const varTypeStr = inspect(varType); onError( @@ -136,6 +143,7 @@ function coerceVariableValues( coerced[varName] = coerceInputValue( value, varType, + shouldProvideSuggestions, (path, invalidValue, error) => { let prefix = `Variable "$${varName}" got invalid value ` + inspect(invalidValue); @@ -159,6 +167,7 @@ export function getFragmentVariableValues( fragmentSpreadNode: FragmentSpreadNode, fragmentSignatures: ReadOnlyObjMap, variableValues: VariableValues, + shouldProvideSuggestions: boolean, fragmentVariableValues?: Maybe, ): VariableValues { const varSignatures: Array = []; @@ -177,6 +186,7 @@ export function getFragmentVariableValues( fragmentSpreadNode, varSignatures, variableValues, + shouldProvideSuggestions, fragmentVariableValues, ); @@ -194,15 +204,22 @@ export function getFragmentVariableValues( export function getArgumentValues( def: GraphQLField | GraphQLDirective, node: FieldNode | DirectiveNode, + shouldProvideSuggestions: boolean, variableValues?: Maybe, ): { [argument: string]: unknown } { - return experimentalGetArgumentValues(node, def.args, variableValues); + return experimentalGetArgumentValues( + node, + def.args, + variableValues, + shouldProvideSuggestions, + ); } export function experimentalGetArgumentValues( node: FieldNode | DirectiveNode | FragmentSpreadNode, argDefs: ReadonlyArray, variableValues: Maybe, + shouldProvideSuggestions: boolean, fragmentVariablesValues?: Maybe, ): { [argument: string]: unknown } { const coercedValues: { [argument: string]: unknown } = {}; @@ -222,6 +239,7 @@ export function experimentalGetArgumentValues( coercedValues[name] = coerceDefaultValue( argDef.defaultValue, argDef.type, + shouldProvideSuggestions, ); } else if (isNonNullType(argType)) { throw new GraphQLError( @@ -251,6 +269,7 @@ export function experimentalGetArgumentValues( coercedValues[name] = coerceDefaultValue( argDef.defaultValue, argDef.type, + shouldProvideSuggestions, ); } else if (isNonNullType(argType)) { throw new GraphQLError( @@ -275,6 +294,7 @@ export function experimentalGetArgumentValues( const coercedValue = coerceInputLiteral( valueNode, argType, + shouldProvideSuggestions, variableValues, fragmentVariablesValues, ); @@ -308,6 +328,7 @@ export function experimentalGetArgumentValues( export function getDirectiveValues( directiveDef: GraphQLDirective, node: { readonly directives?: ReadonlyArray | undefined }, + shouldProvideSuggestions: boolean, variableValues?: Maybe, fragmentVariableValues?: Maybe, ): undefined | { [argument: string]: unknown } { @@ -320,6 +341,7 @@ export function getDirectiveValues( directiveNode, directiveDef.args, variableValues, + shouldProvideSuggestions, fragmentVariableValues, ); } diff --git a/src/type/definition.ts b/src/type/definition.ts index f00e0d56942..cdbfe304fc5 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -1422,12 +1422,15 @@ export class GraphQLEnumType /* */ { return enumValue.name; } - parseValue(inputValue: unknown): Maybe /* T */ { + parseValue( + inputValue: unknown, + shouldProvideSuggestions: boolean, + ): Maybe /* T */ { if (typeof inputValue !== 'string') { const valueStr = inspect(inputValue); throw new GraphQLError( `Enum "${this.name}" cannot represent non-string value: ${valueStr}.` + - didYouMeanEnumValue(this, valueStr), + (shouldProvideSuggestions ? didYouMeanEnumValue(this, valueStr) : ''), ); } @@ -1435,7 +1438,9 @@ export class GraphQLEnumType /* */ { if (enumValue == null) { throw new GraphQLError( `Value "${inputValue}" does not exist in "${this.name}" enum.` + - didYouMeanEnumValue(this, inputValue), + (shouldProvideSuggestions + ? didYouMeanEnumValue(this, inputValue) + : ''), ); } return enumValue.value; @@ -1445,17 +1450,24 @@ export class GraphQLEnumType /* */ { parseLiteral( valueNode: ValueNode, _variables: Maybe>, + shouldProvideSuggestions: boolean, ): Maybe /* T */ { // Note: variables will be resolved to a value before calling this function. - return this.parseConstLiteral(valueNode as ConstValueNode); + return this.parseConstLiteral( + valueNode as ConstValueNode, + shouldProvideSuggestions, + ); } - parseConstLiteral(valueNode: ConstValueNode): Maybe /* T */ { + parseConstLiteral( + valueNode: ConstValueNode, + shouldProvideSuggestions: boolean, + ): Maybe /* T */ { if (valueNode.kind !== Kind.ENUM) { const valueStr = print(valueNode); throw new GraphQLError( `Enum "${this.name}" cannot represent non-enum value: ${valueStr}.` + - didYouMeanEnumValue(this, valueStr), + (shouldProvideSuggestions ? didYouMeanEnumValue(this, valueStr) : ''), { nodes: valueNode }, ); } @@ -1465,7 +1477,7 @@ export class GraphQLEnumType /* */ { const valueStr = print(valueNode); throw new GraphQLError( `Value "${valueStr}" does not exist in "${this.name}" enum.` + - didYouMeanEnumValue(this, valueStr), + (shouldProvideSuggestions ? didYouMeanEnumValue(this, valueStr) : ''), { nodes: valueNode }, ); } diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index c6d10c3b9a5..60c1a6a2582 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -55,6 +55,7 @@ function coerceValue( const value = coerceInputValue( inputValue, type, + true, (path, invalidValue, error) => { errors.push({ path, value: invalidValue, error: error.message }); }, @@ -538,7 +539,7 @@ describe('coerceInputValue', () => { describe('with default onError', () => { it('throw error without path', () => { expect(() => - coerceInputValue(null, new GraphQLNonNull(GraphQLInt)), + coerceInputValue(null, new GraphQLNonNull(GraphQLInt), true), ).to.throw( 'Invalid value null: Expected non-nullable type "Int!" not to be null.', ); @@ -549,6 +550,7 @@ describe('coerceInputValue', () => { coerceInputValue( [null], new GraphQLList(new GraphQLNonNull(GraphQLInt)), + true, ), ).to.throw( 'Invalid value null at "value[0]": Expected non-nullable type "Int!" not to be null.', @@ -565,7 +567,7 @@ describe('coerceInputLiteral', () => { variableValues?: VariableValues, ) { const ast = parseValue(valueText); - const value = coerceInputLiteral(ast, type, variableValues); + const value = coerceInputLiteral(ast, type, true, variableValues); expect(value).to.deep.equal(expected); } @@ -892,10 +894,14 @@ describe('coerceDefaultValue', () => { const defaultValueUsage = { literal: { kind: Kind.STRING, value: 'hello' }, } as const; - expect(coerceDefaultValue(defaultValueUsage, spyScalar)).to.equal('hello'); + expect(coerceDefaultValue(defaultValueUsage, spyScalar, true)).to.equal( + 'hello', + ); // Call a second time - expect(coerceDefaultValue(defaultValueUsage, spyScalar)).to.equal('hello'); + expect(coerceDefaultValue(defaultValueUsage, spyScalar, true)).to.equal( + 'hello', + ); expect(parseValueCalls).to.deep.equal(['hello']); }); }); diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts index cca2010bac8..6c315783d81 100644 --- a/src/utilities/coerceInputValue.ts +++ b/src/utilities/coerceInputValue.ts @@ -43,9 +43,16 @@ type OnErrorCB = ( export function coerceInputValue( inputValue: unknown, type: GraphQLInputType, + shouldProvideSuggestions: boolean, onError: OnErrorCB = defaultOnError, ): unknown { - return coerceInputValueImpl(inputValue, type, onError, undefined); + return coerceInputValueImpl( + inputValue, + type, + onError, + undefined, + shouldProvideSuggestions, + ); } function defaultOnError( @@ -66,10 +73,17 @@ function coerceInputValueImpl( type: GraphQLInputType, onError: OnErrorCB, path: Path | undefined, + shouldProvideSuggestions: boolean, ): unknown { if (isNonNullType(type)) { if (inputValue != null) { - return coerceInputValueImpl(inputValue, type.ofType, onError, path); + return coerceInputValueImpl( + inputValue, + type.ofType, + onError, + path, + shouldProvideSuggestions, + ); } onError( pathToArray(path), @@ -91,11 +105,25 @@ function coerceInputValueImpl( if (isIterableObject(inputValue)) { return Array.from(inputValue, (itemValue, index) => { const itemPath = addPath(path, index, undefined); - return coerceInputValueImpl(itemValue, itemType, onError, itemPath); + return coerceInputValueImpl( + itemValue, + itemType, + onError, + itemPath, + shouldProvideSuggestions, + ); }); } // Lists accept a non-list value as a list of one. - return [coerceInputValueImpl(inputValue, itemType, onError, path)]; + return [ + coerceInputValueImpl( + inputValue, + itemType, + onError, + path, + shouldProvideSuggestions, + ), + ]; } if (isInputObjectType(type)) { @@ -119,6 +147,7 @@ function coerceInputValueImpl( coercedValue[field.name] = coerceDefaultValue( field.defaultValue, field.type, + shouldProvideSuggestions, ); } else if (isNonNullType(field.type)) { const typeStr = inspect(field.type); @@ -138,6 +167,7 @@ function coerceInputValueImpl( field.type, onError, addPath(path, field.name, type.name), + shouldProvideSuggestions, ); } @@ -153,7 +183,7 @@ function coerceInputValueImpl( inputValue, new GraphQLError( `Field "${fieldName}" is not defined by type "${type}".` + - didYouMean(suggestions), + (shouldProvideSuggestions ? didYouMean(suggestions) : ''), ), ); } @@ -192,7 +222,7 @@ function coerceInputValueImpl( // which can throw to indicate failure. If it throws, maintain a reference // to the original error. try { - parseResult = type.parseValue(inputValue); + parseResult = type.parseValue(inputValue, shouldProvideSuggestions); } catch (error) { if (error instanceof GraphQLError) { onError(pathToArray(path), inputValue, error); @@ -230,6 +260,7 @@ function coerceInputValueImpl( export function coerceInputLiteral( valueNode: ValueNode, type: GraphQLInputType, + shouldProvideSuggestions: boolean, variableValues?: Maybe, fragmentVariableValues?: Maybe, ): unknown { @@ -254,6 +285,7 @@ export function coerceInputLiteral( return coerceInputLiteral( valueNode, type.ofType, + shouldProvideSuggestions, variableValues, fragmentVariableValues, ); @@ -269,6 +301,7 @@ export function coerceInputLiteral( const itemValue = coerceInputLiteral( valueNode, type.ofType, + shouldProvideSuggestions, variableValues, fragmentVariableValues, ); @@ -282,6 +315,7 @@ export function coerceInputLiteral( let itemValue = coerceInputLiteral( itemNode, type.ofType, + shouldProvideSuggestions, variableValues, fragmentVariableValues, ); @@ -340,12 +374,14 @@ export function coerceInputLiteral( coercedValue[field.name] = coerceDefaultValue( field.defaultValue, field.type, + shouldProvideSuggestions, ); } } else { const fieldValue = coerceInputLiteral( fieldNode.value, field.type, + shouldProvideSuggestions, variableValues, fragmentVariableValues, ); @@ -375,8 +411,13 @@ export function coerceInputLiteral( return leafType.parseConstLiteral ? leafType.parseConstLiteral( replaceVariables(valueNode, variableValues, fragmentVariableValues), + shouldProvideSuggestions, ) - : leafType.parseLiteral(valueNode, variableValues?.coerced); + : leafType.parseLiteral( + valueNode, + variableValues?.coerced, + shouldProvideSuggestions, + ); } catch (_error) { // Invalid: ignore error and intentionally return no value. } @@ -402,12 +443,13 @@ function getCoercedVariableValue( export function coerceDefaultValue( defaultValue: GraphQLDefaultValueUsage, type: GraphQLInputType, + shouldProvideSuggestions: boolean, ): unknown { // Memoize the result of coercing the default value in a hidden field. let coercedValue = (defaultValue as any)._memoizedCoercedValue; if (coercedValue === undefined) { coercedValue = defaultValue.literal - ? coerceInputLiteral(defaultValue.literal, type) + ? coerceInputLiteral(defaultValue.literal, type, shouldProvideSuggestions) : defaultValue.value; (defaultValue as any)._memoizedCoercedValue = coercedValue; } diff --git a/src/validation/ValidationContext.ts b/src/validation/ValidationContext.ts index d45e7a46a4c..b9c872f9694 100644 --- a/src/validation/ValidationContext.ts +++ b/src/validation/ValidationContext.ts @@ -154,6 +154,10 @@ export class SDLValidationContext extends ASTValidationContext { this._schema = schema; } + get shouldProvideSuggestions() { + return true; + } + get [Symbol.toStringTag]() { return 'SDLValidationContext'; } @@ -177,24 +181,31 @@ export class ValidationContext extends ASTValidationContext { OperationDefinitionNode, ReadonlyArray >; + private _shouldProvideSuggestions: boolean; constructor( schema: GraphQLSchema, ast: DocumentNode, typeInfo: TypeInfo, onError: (error: GraphQLError) => void, + shouldProvideSuggestions?: boolean, ) { super(ast, onError); this._schema = schema; this._typeInfo = typeInfo; this._variableUsages = new Map(); this._recursiveVariableUsages = new Map(); + this._shouldProvideSuggestions = shouldProvideSuggestions ?? true; } get [Symbol.toStringTag]() { return 'ValidationContext'; } + get shouldProvideSuggestions() { + return this._shouldProvideSuggestions; + } + getSchema(): GraphQLSchema { return this._schema; } diff --git a/src/validation/rules/FieldsOnCorrectTypeRule.ts b/src/validation/rules/FieldsOnCorrectTypeRule.ts index c6fce9e89b5..ba7898fa3de 100644 --- a/src/validation/rules/FieldsOnCorrectTypeRule.ts +++ b/src/validation/rules/FieldsOnCorrectTypeRule.ts @@ -45,12 +45,18 @@ export function FieldsOnCorrectTypeRule( // First determine if there are any suggested types to condition on. let suggestion = didYouMean( 'to use an inline fragment on', - getSuggestedTypeNames(schema, type, fieldName), + context.shouldProvideSuggestions + ? getSuggestedTypeNames(schema, type, fieldName) + : [], ); // If there are no suggested types, then perhaps this was a typo? if (suggestion === '') { - suggestion = didYouMean(getSuggestedFieldNames(type, fieldName)); + suggestion = didYouMean( + context.shouldProvideSuggestions + ? getSuggestedFieldNames(type, fieldName) + : [], + ); } // Report an error, including helpful suggestions. diff --git a/src/validation/rules/KnownArgumentNamesRule.ts b/src/validation/rules/KnownArgumentNamesRule.ts index 9e88d5a99d6..cbff2efa218 100644 --- a/src/validation/rules/KnownArgumentNamesRule.ts +++ b/src/validation/rules/KnownArgumentNamesRule.ts @@ -34,12 +34,14 @@ export function KnownArgumentNamesRule(context: ValidationContext): ASTVisitor { ); if (!varDef) { const argName = argNode.name.value; - const suggestions = suggestionList( - argName, - Array.from(fragmentSignature.variableDefinitions.values()).map( - (varSignature) => varSignature.variable.name.value, - ), - ); + const suggestions = context.shouldProvideSuggestions + ? suggestionList( + argName, + Array.from(fragmentSignature.variableDefinitions.values()).map( + (varSignature) => varSignature.variable.name.value, + ), + ) + : []; context.reportError( new GraphQLError( `Unknown argument "${argName}" on fragment "${fragmentSignature.definition.name.value}".` + @@ -57,10 +59,12 @@ export function KnownArgumentNamesRule(context: ValidationContext): ASTVisitor { if (!argDef && fieldDef && parentType) { const argName = argNode.name.value; - const suggestions = suggestionList( - argName, - fieldDef.args.map((arg) => arg.name), - ); + const suggestions = context.shouldProvideSuggestions + ? suggestionList( + argName, + fieldDef.args.map((arg) => arg.name), + ) + : []; context.reportError( new GraphQLError( `Unknown argument "${argName}" on field "${parentType}.${fieldDef.name}".` + diff --git a/src/validation/rules/KnownTypeNamesRule.ts b/src/validation/rules/KnownTypeNamesRule.ts index 789e93eac18..8a4e7d37050 100644 --- a/src/validation/rules/KnownTypeNamesRule.ts +++ b/src/validation/rules/KnownTypeNamesRule.ts @@ -48,10 +48,12 @@ export function KnownTypeNamesRule( return; } - const suggestedTypes = suggestionList( - typeName, - isSDL ? [...standardTypeNames, ...typeNames] : [...typeNames], - ); + const suggestedTypes = context.shouldProvideSuggestions + ? suggestionList( + typeName, + isSDL ? [...standardTypeNames, ...typeNames] : [...typeNames], + ) + : []; context.reportError( new GraphQLError( `Unknown type "${typeName}".` + didYouMean(suggestedTypes), diff --git a/src/validation/rules/PossibleTypeExtensionsRule.ts b/src/validation/rules/PossibleTypeExtensionsRule.ts index d9ccb73cfa8..58ab981544f 100644 --- a/src/validation/rules/PossibleTypeExtensionsRule.ts +++ b/src/validation/rules/PossibleTypeExtensionsRule.ts @@ -78,7 +78,9 @@ export function PossibleTypeExtensionsRule( ...Object.keys(schema?.getTypeMap() ?? {}), ]; - const suggestedTypes = suggestionList(typeName, allTypeNames); + const suggestedTypes = context.shouldProvideSuggestions + ? suggestionList(typeName, allTypeNames) + : []; context.reportError( new GraphQLError( `Cannot extend type "${typeName}" because it is not defined.` + diff --git a/src/validation/rules/ValuesOfCorrectTypeRule.ts b/src/validation/rules/ValuesOfCorrectTypeRule.ts index 73357e1317c..026a172408a 100644 --- a/src/validation/rules/ValuesOfCorrectTypeRule.ts +++ b/src/validation/rules/ValuesOfCorrectTypeRule.ts @@ -97,10 +97,9 @@ export function ValuesOfCorrectTypeRule( const parentType = getNamedType(context.getParentInputType()); const fieldType = context.getInputType(); if (!fieldType && isInputObjectType(parentType)) { - const suggestions = suggestionList( - node.name.value, - Object.keys(parentType.getFields()), - ); + const suggestions = context.shouldProvideSuggestions + ? suggestionList(node.name.value, Object.keys(parentType.getFields())) + : []; context.reportError( new GraphQLError( `Field "${node.name.value}" is not defined by type "${parentType}".` + @@ -157,8 +156,11 @@ function isValidValueNode(context: ValidationContext, node: ValueNode): void { // which may throw or return undefined to indicate an invalid value. try { const parseResult = type.parseConstLiteral - ? type.parseConstLiteral(replaceVariables(node)) - : type.parseLiteral(node, undefined); + ? type.parseConstLiteral( + replaceVariables(node), + context.shouldProvideSuggestions, + ) + : type.parseLiteral(node, undefined, context.shouldProvideSuggestions); if (parseResult === undefined) { const typeStr = inspect(locationType); context.reportError( diff --git a/src/validation/validate.ts b/src/validation/validate.ts index e380d167d99..6a5da134846 100644 --- a/src/validation/validate.ts +++ b/src/validation/validate.ts @@ -41,9 +41,10 @@ export function validate( schema: GraphQLSchema, documentAST: DocumentNode, rules: ReadonlyArray = specifiedRules, - options?: { maxErrors?: number }, + options?: { maxErrors?: number; shouldProvideSuggestions?: boolean }, ): ReadonlyArray { const maxErrors = options?.maxErrors ?? 100; + const shouldProvideSuggestions = options?.shouldProvideSuggestions ?? true; // If the schema used for validation is invalid, throw an error. assertValidSchema(schema); @@ -63,6 +64,7 @@ export function validate( } errors.push(error); }, + shouldProvideSuggestions, ); // This uses a specialized visitor which runs multiple visitors in parallel, From ff2087db75ea5db136c12bb5ac165066fbf33bf9 Mon Sep 17 00:00:00 2001 From: jdecroock Date: Mon, 7 Oct 2024 12:57:44 +0200 Subject: [PATCH 2/4] Add tests --- src/execution/collectFields.ts | 29 ++------ src/execution/execute.ts | 41 +++++------- src/execution/values.ts | 32 ++++----- src/graphql.ts | 7 +- src/type/__tests__/enumType-test.ts | 25 ++++++- src/type/definition.ts | 21 +++--- .../__tests__/coerceInputValue-test.ts | 63 +++++++++++++++++- src/utilities/coerceInputValue.ts | 46 +++++++------ src/utilities/valueFromAST.ts | 2 +- src/validation/ValidationContext.ts | 14 ++-- .../__tests__/FieldsOnCorrectTypeRule-test.ts | 19 +++++- .../__tests__/KnownArgumentNamesRule-test.ts | 62 ++++++++++++++++- .../__tests__/KnownTypeNamesRule-test.ts | 34 +++++++++- .../__tests__/ValuesOfCorrectTypeRule-test.ts | 66 ++++++++++++++++++- src/validation/__tests__/harness.ts | 11 +++- .../rules/FieldsOnCorrectTypeRule.ts | 12 ++-- .../rules/KnownArgumentNamesRule.ts | 18 ++--- src/validation/rules/KnownTypeNamesRule.ts | 8 +-- .../rules/PossibleTypeExtensionsRule.ts | 5 +- .../rules/SingleFieldSubscriptionsRule.ts | 1 + .../rules/ValuesOfCorrectTypeRule.ts | 16 ++--- src/validation/validate.ts | 6 +- 22 files changed, 388 insertions(+), 150 deletions(-) diff --git a/src/execution/collectFields.ts b/src/execution/collectFields.ts index a377edc0684..65d1cb48e00 100644 --- a/src/execution/collectFields.ts +++ b/src/execution/collectFields.ts @@ -55,7 +55,7 @@ interface CollectFieldsContext { operation: OperationDefinitionNode; runtimeType: GraphQLObjectType; visitedFragmentNames: Set; - shouldProvideSuggestions: boolean; + maskSuggestions: boolean; } /** @@ -74,7 +74,7 @@ export function collectFields( variableValues: VariableValues, runtimeType: GraphQLObjectType, operation: OperationDefinitionNode, - shouldProvideSuggestions: boolean, + maskSuggestions: boolean, ): { groupedFieldSet: GroupedFieldSet; newDeferUsages: ReadonlyArray; @@ -88,7 +88,7 @@ export function collectFields( runtimeType, operation, visitedFragmentNames: new Set(), - shouldProvideSuggestions, + maskSuggestions, }; collectFieldsImpl( @@ -118,7 +118,7 @@ export function collectSubfields( operation: OperationDefinitionNode, returnType: GraphQLObjectType, fieldDetailsList: FieldDetailsList, - shouldProvideSuggestions: boolean, + maskSuggestions: boolean, ): { groupedFieldSet: GroupedFieldSet; newDeferUsages: ReadonlyArray; @@ -130,7 +130,7 @@ export function collectSubfields( runtimeType: returnType, operation, visitedFragmentNames: new Set(), - shouldProvideSuggestions, + maskSuggestions, }; const subGroupedFieldSet = new AccumulatorMap(); const newDeferUsages: Array = []; @@ -178,12 +178,7 @@ function collectFieldsImpl( switch (selection.kind) { case Kind.FIELD: { if ( - !shouldIncludeNode( - selection, - variableValues, - fragmentVariableValues, - context.shouldProvideSuggestions, - ) + !shouldIncludeNode(selection, variableValues, fragmentVariableValues) ) { continue; } @@ -200,7 +195,6 @@ function collectFieldsImpl( selection, variableValues, fragmentVariableValues, - context.shouldProvideSuggestions, ) || !doesFragmentConditionMatch(schema, selection, runtimeType) ) { @@ -213,7 +207,6 @@ function collectFieldsImpl( fragmentVariableValues, selection, deferUsage, - context.shouldProvideSuggestions, ); if (!newDeferUsage) { @@ -248,7 +241,6 @@ function collectFieldsImpl( fragmentVariableValues, selection, deferUsage, - context.shouldProvideSuggestions, ); if ( @@ -258,7 +250,6 @@ function collectFieldsImpl( selection, variableValues, fragmentVariableValues, - context.shouldProvideSuggestions, )) ) { continue; @@ -279,8 +270,8 @@ function collectFieldsImpl( selection, fragmentVariableSignatures, variableValues, - context.shouldProvideSuggestions, fragmentVariableValues, + false, ); } @@ -316,19 +307,16 @@ function collectFieldsImpl( * deferred based on the experimental flag, defer directive present and * not disabled by the "if" argument. */ -// eslint-disable-next-line @typescript-eslint/max-params function getDeferUsage( operation: OperationDefinitionNode, variableValues: VariableValues, fragmentVariableValues: VariableValues | undefined, node: FragmentSpreadNode | InlineFragmentNode, parentDeferUsage: DeferUsage | undefined, - shouldProvideSuggestions: boolean, ): DeferUsage | undefined { const defer = getDirectiveValues( GraphQLDeferDirective, node, - shouldProvideSuggestions, variableValues, fragmentVariableValues, ); @@ -360,12 +348,10 @@ function shouldIncludeNode( node: FragmentSpreadNode | FieldNode | InlineFragmentNode, variableValues: VariableValues, fragmentVariableValues: VariableValues | undefined, - shouldProvideSuggestions: boolean, ): boolean { const skip = getDirectiveValues( GraphQLSkipDirective, node, - shouldProvideSuggestions, variableValues, fragmentVariableValues, ); @@ -376,7 +362,6 @@ function shouldIncludeNode( const include = getDirectiveValues( GraphQLIncludeDirective, node, - shouldProvideSuggestions, variableValues, fragmentVariableValues, ); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 0c884fd8e5c..2b6705dc4d5 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -98,13 +98,8 @@ const collectSubfields = memoize3( returnType: GraphQLObjectType, fieldDetailsList: FieldDetailsList, ) => { - const { - schema, - fragments, - operation, - variableValues, - shouldProvideSuggestions, - } = validatedExecutionArgs; + const { schema, fragments, operation, variableValues, maskSuggestions } = + validatedExecutionArgs; return _collectSubfields( schema, fragments, @@ -112,7 +107,7 @@ const collectSubfields = memoize3( operation, returnType, fieldDetailsList, - shouldProvideSuggestions, + maskSuggestions, ); }, ); @@ -161,7 +156,7 @@ export interface ValidatedExecutionArgs { validatedExecutionArgs: ValidatedExecutionArgs, ) => PromiseOrValue; enableEarlyExecution: boolean; - shouldProvideSuggestions: boolean; + maskSuggestions: boolean; } export interface ExecutionContext { @@ -191,7 +186,7 @@ export interface ExecutionArgs { ) => PromiseOrValue >; enableEarlyExecution?: Maybe; - shouldProvideSuggestions?: Maybe; + maskSuggestions?: Maybe; } export interface StreamUsage { @@ -322,7 +317,7 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent( rootValue, operation, variableValues, - shouldProvideSuggestions, + maskSuggestions, } = validatedExecutionArgs; const rootType = schema.getRootType(operation.operation); if (rootType == null) { @@ -338,7 +333,7 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent( variableValues, rootType, operation, - shouldProvideSuggestions, + maskSuggestions, ); const { groupedFieldSet, newDeferUsages } = collectedFields; @@ -516,7 +511,6 @@ export function validateExecutionArgs( subscribeFieldResolver, perEventExecutor, enableEarlyExecution, - shouldProvideSuggestions, } = args; // If the schema used for execution is invalid, throw an error. @@ -570,6 +564,7 @@ export function validateExecutionArgs( // FIXME: https://github.com/graphql/graphql-js/issues/2203 /* c8 ignore next */ const variableDefinitions = operation.variableDefinitions ?? []; + const maskSuggestions = args.maskSuggestions ?? false; const variableValuesOrErrors = getVariableValues( schema, @@ -577,7 +572,7 @@ export function validateExecutionArgs( rawVariableValues ?? {}, { maxErrors: 50, - shouldProvideSuggestions: shouldProvideSuggestions ?? true, + maskSuggestions, }, ); @@ -598,7 +593,7 @@ export function validateExecutionArgs( subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver, perEventExecutor: perEventExecutor ?? executeSubscriptionEvent, enableEarlyExecution: enableEarlyExecution === true, - shouldProvideSuggestions: shouldProvideSuggestions ?? true, + maskSuggestions, }; } @@ -782,7 +777,8 @@ function executeField( deferMap: ReadonlyMap | undefined, ): PromiseOrValue> | undefined { const validatedExecutionArgs = exeContext.validatedExecutionArgs; - const { schema, contextValue, variableValues, shouldProvideSuggestions } = validatedExecutionArgs; + const { schema, contextValue, variableValues, maskSuggestions } = + validatedExecutionArgs; const fieldName = fieldDetailsList[0].node.name.value; const fieldDef = schema.getField(parentType, fieldName); if (!fieldDef) { @@ -809,8 +805,8 @@ function executeField( fieldDetailsList[0].node, fieldDef.args, variableValues, - shouldProvideSuggestions, fieldDetailsList[0].fragmentVariableValues, + maskSuggestions, ); // The resolve function's optional third argument is a context value that @@ -1114,16 +1110,15 @@ function getStreamUsage( ._streamUsage; } - const { operation, variableValues, shouldProvideSuggestions } = - validatedExecutionArgs; + const { operation, variableValues, maskSuggestions } = validatedExecutionArgs; // validation only allows equivalent streams on multiple fields, so it is // safe to only check the first fieldNode for the stream directive const stream = getDirectiveValues( GraphQLStreamDirective, fieldDetailsList[0].node, - shouldProvideSuggestions, variableValues, fieldDetailsList[0].fragmentVariableValues, + maskSuggestions, ); if (!stream) { @@ -2088,7 +2083,7 @@ function executeSubscription( contextValue, operation, variableValues, - shouldProvideSuggestions, + maskSuggestions, } = validatedExecutionArgs; const rootType = schema.getSubscriptionType(); @@ -2105,7 +2100,7 @@ function executeSubscription( variableValues, rootType, operation, - shouldProvideSuggestions, + maskSuggestions, ); const firstRootField = groupedFieldSet.entries().next().value as [ @@ -2142,8 +2137,8 @@ function executeSubscription( const args = getArgumentValues( fieldDef, fieldNodes[0], - validatedExecutionArgs.shouldProvideSuggestions, variableValues, + maskSuggestions, ); // Call the `subscribe()` resolver or the default resolver to produce an diff --git a/src/execution/values.ts b/src/execution/values.ts index d2cdc8bfdc5..d7db674867f 100644 --- a/src/execution/values.ts +++ b/src/execution/values.ts @@ -55,11 +55,10 @@ export function getVariableValues( schema: GraphQLSchema, varDefNodes: ReadonlyArray, inputs: { readonly [variable: string]: unknown }, - options?: { maxErrors?: number; shouldProvideSuggestions?: boolean }, + options?: { maxErrors?: number; maskSuggestions?: boolean }, ): VariableValuesOrErrors { const errors: Array = []; const maxErrors = options?.maxErrors; - const shouldProvideSuggestions = options?.shouldProvideSuggestions ?? true; try { const variableValues = coerceVariableValues( schema, @@ -73,7 +72,7 @@ export function getVariableValues( } errors.push(error); }, - shouldProvideSuggestions, + options?.maskSuggestions, ); if (errors.length === 0) { @@ -91,7 +90,7 @@ function coerceVariableValues( varDefNodes: ReadonlyArray, inputs: { readonly [variable: string]: unknown }, onError: (error: GraphQLError) => void, - shouldProvideSuggestions: boolean, + maskSuggestions?: Maybe, ): VariableValues { const sources: ObjMap = Object.create(null); const coerced: ObjMap = Object.create(null); @@ -111,7 +110,7 @@ function coerceVariableValues( coerced[varName] = coerceDefaultValue( defaultValue, varType, - shouldProvideSuggestions, + maskSuggestions, ); } else if (isNonNullType(varType)) { const varTypeStr = inspect(varType); @@ -143,7 +142,6 @@ function coerceVariableValues( coerced[varName] = coerceInputValue( value, varType, - shouldProvideSuggestions, (path, invalidValue, error) => { let prefix = `Variable "$${varName}" got invalid value ` + inspect(invalidValue); @@ -157,6 +155,7 @@ function coerceVariableValues( }), ); }, + maskSuggestions, ); } @@ -167,8 +166,8 @@ export function getFragmentVariableValues( fragmentSpreadNode: FragmentSpreadNode, fragmentSignatures: ReadOnlyObjMap, variableValues: VariableValues, - shouldProvideSuggestions: boolean, fragmentVariableValues?: Maybe, + maskSuggestions?: Maybe, ): VariableValues { const varSignatures: Array = []; const sources = Object.create(null); @@ -186,8 +185,8 @@ export function getFragmentVariableValues( fragmentSpreadNode, varSignatures, variableValues, - shouldProvideSuggestions, fragmentVariableValues, + maskSuggestions, ); return { sources, coerced }; @@ -204,14 +203,15 @@ export function getFragmentVariableValues( export function getArgumentValues( def: GraphQLField | GraphQLDirective, node: FieldNode | DirectiveNode, - shouldProvideSuggestions: boolean, variableValues?: Maybe, + maskSuggestions?: Maybe, ): { [argument: string]: unknown } { return experimentalGetArgumentValues( node, def.args, variableValues, - shouldProvideSuggestions, + undefined, + maskSuggestions, ); } @@ -219,8 +219,8 @@ export function experimentalGetArgumentValues( node: FieldNode | DirectiveNode | FragmentSpreadNode, argDefs: ReadonlyArray, variableValues: Maybe, - shouldProvideSuggestions: boolean, fragmentVariablesValues?: Maybe, + maskSuggestions?: Maybe, ): { [argument: string]: unknown } { const coercedValues: { [argument: string]: unknown } = {}; @@ -239,7 +239,7 @@ export function experimentalGetArgumentValues( coercedValues[name] = coerceDefaultValue( argDef.defaultValue, argDef.type, - shouldProvideSuggestions, + maskSuggestions, ); } else if (isNonNullType(argType)) { throw new GraphQLError( @@ -269,7 +269,7 @@ export function experimentalGetArgumentValues( coercedValues[name] = coerceDefaultValue( argDef.defaultValue, argDef.type, - shouldProvideSuggestions, + maskSuggestions, ); } else if (isNonNullType(argType)) { throw new GraphQLError( @@ -294,9 +294,9 @@ export function experimentalGetArgumentValues( const coercedValue = coerceInputLiteral( valueNode, argType, - shouldProvideSuggestions, variableValues, fragmentVariablesValues, + maskSuggestions, ); if (coercedValue === undefined) { // Note: ValuesOfCorrectTypeRule validation should catch this before @@ -328,9 +328,9 @@ export function experimentalGetArgumentValues( export function getDirectiveValues( directiveDef: GraphQLDirective, node: { readonly directives?: ReadonlyArray | undefined }, - shouldProvideSuggestions: boolean, variableValues?: Maybe, fragmentVariableValues?: Maybe, + maskSuggestions?: Maybe, ): undefined | { [argument: string]: unknown } { const directiveNode = node.directives?.find( (directive) => directive.name.value === directiveDef.name, @@ -341,8 +341,8 @@ export function getDirectiveValues( directiveNode, directiveDef.args, variableValues, - shouldProvideSuggestions, fragmentVariableValues, + maskSuggestions, ); } } diff --git a/src/graphql.ts b/src/graphql.ts index 7596cf524fb..376798e783d 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -61,6 +61,7 @@ import type { ExecutionResult } from './execution/types.js'; export interface GraphQLArgs { schema: GraphQLSchema; source: string | Source; + maskSuggestions?: boolean; rootValue?: unknown; contextValue?: unknown; variableValues?: Maybe<{ readonly [variable: string]: unknown }>; @@ -101,6 +102,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + maskSuggestions, } = args; // Validate Schema @@ -118,7 +120,9 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { } // Validate - const validationErrors = validate(schema, document); + const validationErrors = validate(schema, document, undefined, { + maskSuggestions, + }); if (validationErrors.length > 0) { return { errors: validationErrors }; } @@ -133,5 +137,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { operationName, fieldResolver, typeResolver, + maskSuggestions, }); } diff --git a/src/type/__tests__/enumType-test.ts b/src/type/__tests__/enumType-test.ts index 8cc43fabd8d..da5c496e05f 100644 --- a/src/type/__tests__/enumType-test.ts +++ b/src/type/__tests__/enumType-test.ts @@ -135,8 +135,14 @@ const schema = new GraphQLSchema({ function executeQuery( source: string, variableValues?: { readonly [variable: string]: unknown }, + maskSuggestions = false, ) { - return graphqlSync({ schema, source, variableValues }); + return graphqlSync({ + schema, + source, + variableValues, + maskSuggestions, + }); } describe('Type System: Enum Values', () => { @@ -192,6 +198,23 @@ describe('Type System: Enum Values', () => { }); }); + it('does not accept values not in the enum (no suggestions)', () => { + const result = executeQuery( + '{ colorEnum(fromEnum: GREENISH) }', + undefined, + true, + ); + + expectJSON(result).toDeepEqual({ + errors: [ + { + message: 'Value "GREENISH" does not exist in "Color" enum.', + locations: [{ line: 1, column: 23 }], + }, + ], + }); + }); + it('does not accept values with incorrect casing', () => { const result = executeQuery('{ colorEnum(fromEnum: green) }'); diff --git a/src/type/definition.ts b/src/type/definition.ts index cdbfe304fc5..04b1f338d30 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -1424,13 +1424,13 @@ export class GraphQLEnumType /* */ { parseValue( inputValue: unknown, - shouldProvideSuggestions: boolean, + maskSuggestions?: Maybe, ): Maybe /* T */ { if (typeof inputValue !== 'string') { const valueStr = inspect(inputValue); throw new GraphQLError( `Enum "${this.name}" cannot represent non-string value: ${valueStr}.` + - (shouldProvideSuggestions ? didYouMeanEnumValue(this, valueStr) : ''), + (maskSuggestions ? '' : didYouMeanEnumValue(this, valueStr)), ); } @@ -1438,9 +1438,7 @@ export class GraphQLEnumType /* */ { if (enumValue == null) { throw new GraphQLError( `Value "${inputValue}" does not exist in "${this.name}" enum.` + - (shouldProvideSuggestions - ? didYouMeanEnumValue(this, inputValue) - : ''), + (maskSuggestions ? '' : didYouMeanEnumValue(this, inputValue)), ); } return enumValue.value; @@ -1450,24 +1448,21 @@ export class GraphQLEnumType /* */ { parseLiteral( valueNode: ValueNode, _variables: Maybe>, - shouldProvideSuggestions: boolean, + maskSuggestions?: Maybe, ): Maybe /* T */ { // Note: variables will be resolved to a value before calling this function. - return this.parseConstLiteral( - valueNode as ConstValueNode, - shouldProvideSuggestions, - ); + return this.parseConstLiteral(valueNode as ConstValueNode, maskSuggestions); } parseConstLiteral( valueNode: ConstValueNode, - shouldProvideSuggestions: boolean, + maskSuggestions?: Maybe, ): Maybe /* T */ { if (valueNode.kind !== Kind.ENUM) { const valueStr = print(valueNode); throw new GraphQLError( `Enum "${this.name}" cannot represent non-enum value: ${valueStr}.` + - (shouldProvideSuggestions ? didYouMeanEnumValue(this, valueStr) : ''), + (maskSuggestions ? '' : didYouMeanEnumValue(this, valueStr)), { nodes: valueNode }, ); } @@ -1477,7 +1472,7 @@ export class GraphQLEnumType /* */ { const valueStr = print(valueNode); throw new GraphQLError( `Value "${valueStr}" does not exist in "${this.name}" enum.` + - (shouldProvideSuggestions ? didYouMeanEnumValue(this, valueStr) : ''), + (maskSuggestions ? '' : didYouMeanEnumValue(this, valueStr)), { nodes: valueNode }, ); } diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index 60c1a6a2582..3ba8b3b8347 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -50,15 +50,16 @@ interface CoerceError { function coerceValue( inputValue: unknown, type: GraphQLInputType, + maskSuggestions: boolean = false, ): CoerceResult { const errors: Array = []; const value = coerceInputValue( inputValue, type, - true, (path, invalidValue, error) => { errors.push({ path, value: invalidValue, error: error.message }); }, + maskSuggestions, ); return { errors, value }; @@ -184,6 +185,17 @@ describe('coerceInputValue', () => { ]); }); + it('returns an error for misspelled enum value (no suggestions)', () => { + const result = coerceValue('foo', TestEnum, true); + expectErrors(result).to.deep.equal([ + { + error: 'Value "foo" does not exist in "TestEnum" enum.', + path: [], + value: 'foo', + }, + ]); + }); + it('returns an error for incorrect value type', () => { const result1 = coerceValue(123, TestEnum); expectErrors(result1).to.deep.equal([ @@ -204,6 +216,27 @@ describe('coerceInputValue', () => { }, ]); }); + + it('returns an error for incorrect value type (no suggestions)', () => { + const result1 = coerceValue(123, TestEnum, true); + expectErrors(result1).to.deep.equal([ + { + error: 'Enum "TestEnum" cannot represent non-string value: 123.', + path: [], + value: 123, + }, + ]); + + const result2 = coerceValue({ field: 'value' }, TestEnum, false); + expectErrors(result2).to.deep.equal([ + { + error: + 'Enum "TestEnum" cannot represent non-string value: { field: "value" }.', + path: [], + value: { field: 'value' }, + }, + ]); + }); }); describe('for GraphQLInputObject', () => { @@ -401,6 +434,23 @@ describe('coerceInputValue', () => { }, ]); }); + + it('returns error for a misspelled field without suggestions', () => { + const result = coerceValue({ bart: 123 }, TestInputObject, true); + expectErrors(result).to.deep.equal([ + { + error: 'Field "bart" is not defined by type "TestInputObject".', + path: [], + value: { bart: 123 }, + }, + { + error: + 'Exactly one key must be specified for OneOf type "TestInputObject".', + path: [], + value: { bart: 123 }, + }, + ]); + }); }); describe('for GraphQLInputObject with default value', () => { @@ -539,7 +589,7 @@ describe('coerceInputValue', () => { describe('with default onError', () => { it('throw error without path', () => { expect(() => - coerceInputValue(null, new GraphQLNonNull(GraphQLInt), true), + coerceInputValue(null, new GraphQLNonNull(GraphQLInt), undefined, true), ).to.throw( 'Invalid value null: Expected non-nullable type "Int!" not to be null.', ); @@ -550,6 +600,7 @@ describe('coerceInputValue', () => { coerceInputValue( [null], new GraphQLList(new GraphQLNonNull(GraphQLInt)), + undefined, true, ), ).to.throw( @@ -567,7 +618,13 @@ describe('coerceInputLiteral', () => { variableValues?: VariableValues, ) { const ast = parseValue(valueText); - const value = coerceInputLiteral(ast, type, true, variableValues); + const value = coerceInputLiteral( + ast, + type, + variableValues, + undefined, + true, + ); expect(value).to.deep.equal(expected); } diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts index 6c315783d81..e94c3d1f557 100644 --- a/src/utilities/coerceInputValue.ts +++ b/src/utilities/coerceInputValue.ts @@ -43,15 +43,15 @@ type OnErrorCB = ( export function coerceInputValue( inputValue: unknown, type: GraphQLInputType, - shouldProvideSuggestions: boolean, onError: OnErrorCB = defaultOnError, + maskSuggestions?: Maybe, ): unknown { return coerceInputValueImpl( inputValue, type, onError, undefined, - shouldProvideSuggestions, + maskSuggestions, ); } @@ -73,7 +73,7 @@ function coerceInputValueImpl( type: GraphQLInputType, onError: OnErrorCB, path: Path | undefined, - shouldProvideSuggestions: boolean, + maskSuggestions?: Maybe, ): unknown { if (isNonNullType(type)) { if (inputValue != null) { @@ -82,7 +82,7 @@ function coerceInputValueImpl( type.ofType, onError, path, - shouldProvideSuggestions, + maskSuggestions, ); } onError( @@ -110,7 +110,7 @@ function coerceInputValueImpl( itemType, onError, itemPath, - shouldProvideSuggestions, + maskSuggestions, ); }); } @@ -121,7 +121,7 @@ function coerceInputValueImpl( itemType, onError, path, - shouldProvideSuggestions, + maskSuggestions, ), ]; } @@ -147,7 +147,7 @@ function coerceInputValueImpl( coercedValue[field.name] = coerceDefaultValue( field.defaultValue, field.type, - shouldProvideSuggestions, + maskSuggestions, ); } else if (isNonNullType(field.type)) { const typeStr = inspect(field.type); @@ -167,7 +167,7 @@ function coerceInputValueImpl( field.type, onError, addPath(path, field.name, type.name), - shouldProvideSuggestions, + maskSuggestions, ); } @@ -183,7 +183,7 @@ function coerceInputValueImpl( inputValue, new GraphQLError( `Field "${fieldName}" is not defined by type "${type}".` + - (shouldProvideSuggestions ? didYouMean(suggestions) : ''), + (maskSuggestions ? '' : didYouMean(suggestions)), ), ); } @@ -222,7 +222,7 @@ function coerceInputValueImpl( // which can throw to indicate failure. If it throws, maintain a reference // to the original error. try { - parseResult = type.parseValue(inputValue, shouldProvideSuggestions); + parseResult = type.parseValue(inputValue, maskSuggestions); } catch (error) { if (error instanceof GraphQLError) { onError(pathToArray(path), inputValue, error); @@ -260,9 +260,9 @@ function coerceInputValueImpl( export function coerceInputLiteral( valueNode: ValueNode, type: GraphQLInputType, - shouldProvideSuggestions: boolean, variableValues?: Maybe, fragmentVariableValues?: Maybe, + maskSuggestions?: Maybe, ): unknown { if (valueNode.kind === Kind.VARIABLE) { const coercedVariableValue = getCoercedVariableValue( @@ -285,9 +285,9 @@ export function coerceInputLiteral( return coerceInputLiteral( valueNode, type.ofType, - shouldProvideSuggestions, variableValues, fragmentVariableValues, + maskSuggestions, ); } @@ -301,9 +301,9 @@ export function coerceInputLiteral( const itemValue = coerceInputLiteral( valueNode, type.ofType, - shouldProvideSuggestions, variableValues, fragmentVariableValues, + maskSuggestions, ); if (itemValue === undefined) { return; // Invalid: intentionally return no value. @@ -315,9 +315,9 @@ export function coerceInputLiteral( let itemValue = coerceInputLiteral( itemNode, type.ofType, - shouldProvideSuggestions, variableValues, fragmentVariableValues, + maskSuggestions, ); if (itemValue === undefined) { if ( @@ -374,16 +374,16 @@ export function coerceInputLiteral( coercedValue[field.name] = coerceDefaultValue( field.defaultValue, field.type, - shouldProvideSuggestions, + maskSuggestions, ); } } else { const fieldValue = coerceInputLiteral( fieldNode.value, field.type, - shouldProvideSuggestions, variableValues, fragmentVariableValues, + maskSuggestions, ); if (fieldValue === undefined) { return; // Invalid: intentionally return no value. @@ -411,12 +411,12 @@ export function coerceInputLiteral( return leafType.parseConstLiteral ? leafType.parseConstLiteral( replaceVariables(valueNode, variableValues, fragmentVariableValues), - shouldProvideSuggestions, + maskSuggestions, ) : leafType.parseLiteral( valueNode, variableValues?.coerced, - shouldProvideSuggestions, + maskSuggestions, ); } catch (_error) { // Invalid: ignore error and intentionally return no value. @@ -443,13 +443,19 @@ function getCoercedVariableValue( export function coerceDefaultValue( defaultValue: GraphQLDefaultValueUsage, type: GraphQLInputType, - shouldProvideSuggestions: boolean, + maskSuggestions?: Maybe, ): unknown { // Memoize the result of coercing the default value in a hidden field. let coercedValue = (defaultValue as any)._memoizedCoercedValue; if (coercedValue === undefined) { coercedValue = defaultValue.literal - ? coerceInputLiteral(defaultValue.literal, type, shouldProvideSuggestions) + ? coerceInputLiteral( + defaultValue.literal, + type, + undefined, + undefined, + maskSuggestions, + ) : defaultValue.value; (defaultValue as any)._memoizedCoercedValue = coercedValue; } diff --git a/src/utilities/valueFromAST.ts b/src/utilities/valueFromAST.ts index e0ff5d8bf21..c871d39e71d 100644 --- a/src/utilities/valueFromAST.ts +++ b/src/utilities/valueFromAST.ts @@ -148,7 +148,7 @@ export function valueFromAST( // no value is returned. let result; try { - result = type.parseLiteral(valueNode, variables); + result = type.parseLiteral(valueNode, variables, true); } catch (_error) { return; // Invalid: intentionally return no value. } diff --git a/src/validation/ValidationContext.ts b/src/validation/ValidationContext.ts index b9c872f9694..803aa7365f3 100644 --- a/src/validation/ValidationContext.ts +++ b/src/validation/ValidationContext.ts @@ -154,8 +154,8 @@ export class SDLValidationContext extends ASTValidationContext { this._schema = schema; } - get shouldProvideSuggestions() { - return true; + get maskSuggestions() { + return false; } get [Symbol.toStringTag]() { @@ -181,29 +181,29 @@ export class ValidationContext extends ASTValidationContext { OperationDefinitionNode, ReadonlyArray >; - private _shouldProvideSuggestions: boolean; + private _maskSuggestions: boolean; constructor( schema: GraphQLSchema, ast: DocumentNode, typeInfo: TypeInfo, onError: (error: GraphQLError) => void, - shouldProvideSuggestions?: boolean, + maskSuggestions?: Maybe, ) { super(ast, onError); this._schema = schema; this._typeInfo = typeInfo; this._variableUsages = new Map(); this._recursiveVariableUsages = new Map(); - this._shouldProvideSuggestions = shouldProvideSuggestions ?? true; + this._maskSuggestions = maskSuggestions ?? false; } get [Symbol.toStringTag]() { return 'ValidationContext'; } - get shouldProvideSuggestions() { - return this._shouldProvideSuggestions; + get maskSuggestions() { + return this._maskSuggestions; } getSchema(): GraphQLSchema { diff --git a/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts b/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts index 1c7fbc0351c..a0461071a4d 100644 --- a/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts +++ b/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts @@ -12,11 +12,12 @@ import { validate } from '../validate.js'; import { expectValidationErrorsWithSchema } from './harness.js'; -function expectErrors(queryStr: string) { +function expectErrors(queryStr: string, maskSuggestions = false) { return expectValidationErrorsWithSchema( testSchema, FieldsOnCorrectTypeRule, queryStr, + maskSuggestions, ); } @@ -140,6 +141,22 @@ describe('Validate: Fields on correct type', () => { ]); }); + it('Field not defined on fragment (no suggestions)', () => { + expectErrors( + ` + fragment fieldNotDefined on Dog { + meowVolume + } + `, + true, + ).toDeepEqual([ + { + message: 'Cannot query field "meowVolume" on type "Dog".', + locations: [{ line: 3, column: 9 }], + }, + ]); + }); + it('Ignores deeply unknown field', () => { expectErrors(` fragment deepFieldNotDefined on Dog { diff --git a/src/validation/__tests__/KnownArgumentNamesRule-test.ts b/src/validation/__tests__/KnownArgumentNamesRule-test.ts index 28e3b564cba..945161ae4a1 100644 --- a/src/validation/__tests__/KnownArgumentNamesRule-test.ts +++ b/src/validation/__tests__/KnownArgumentNamesRule-test.ts @@ -14,8 +14,12 @@ import { expectValidationErrors, } from './harness.js'; -function expectErrors(queryStr: string) { - return expectValidationErrors(KnownArgumentNamesRule, queryStr); +function expectErrors(queryStr: string, maskSuggestions = false) { + return expectValidationErrors( + KnownArgumentNamesRule, + queryStr, + maskSuggestions, + ); } function expectValid(queryStr: string) { @@ -161,6 +165,22 @@ describe('Validate: Known argument names', () => { ]); }); + it('misspelled directive args are reported (no suggestions)', () => { + expectErrors( + ` + { + dog @skip(iff: true) + } + `, + true, + ).toDeepEqual([ + { + message: 'Unknown argument "iff" on directive "@skip".', + locations: [{ line: 3, column: 19 }], + }, + ]); + }); + it('arg passed to fragment without arg is reported', () => { expectErrors(` { @@ -198,6 +218,27 @@ describe('Validate: Known argument names', () => { ]); }); + it('misspelled fragment args are reported (no suggestions)', () => { + expectErrors( + ` + { + dog { + ...withArg(command: SIT) + } + } + fragment withArg($dogCommand: DogCommand) on Dog { + doesKnowCommand(dogCommand: $dogCommand) + } + `, + true, + ).toDeepEqual([ + { + message: 'Unknown argument "command" on fragment "withArg".', + locations: [{ line: 4, column: 22 }], + }, + ]); + }); + it('invalid arg name', () => { expectErrors(` fragment invalidArgName on Dog { @@ -225,6 +266,23 @@ describe('Validate: Known argument names', () => { ]); }); + it('misspelled arg name is reported (no suggestions)', () => { + expectErrors( + ` + fragment invalidArgName on Dog { + doesKnowCommand(DogCommand: true) + } + `, + true, + ).toDeepEqual([ + { + message: + 'Unknown argument "DogCommand" on field "Dog.doesKnowCommand".', + locations: [{ line: 3, column: 25 }], + }, + ]); + }); + it('unknown args amongst known args', () => { expectErrors(` fragment oneGoodArgOneInvalidArg on Dog { diff --git a/src/validation/__tests__/KnownTypeNamesRule-test.ts b/src/validation/__tests__/KnownTypeNamesRule-test.ts index 0440c094d0b..7213fcf3129 100644 --- a/src/validation/__tests__/KnownTypeNamesRule-test.ts +++ b/src/validation/__tests__/KnownTypeNamesRule-test.ts @@ -12,8 +12,8 @@ import { expectValidationErrorsWithSchema, } from './harness.js'; -function expectErrors(queryStr: string) { - return expectValidationErrors(KnownTypeNamesRule, queryStr); +function expectErrors(queryStr: string, maskSuggestions = false) { + return expectValidationErrors(KnownTypeNamesRule, queryStr, maskSuggestions); } function expectErrorsWithSchema(schema: GraphQLSchema, queryStr: string) { @@ -78,6 +78,36 @@ describe('Validate: Known type names', () => { ]); }); + it('unknown type names are invalid (no suggestions)', () => { + expectErrors( + ` + query Foo($var: [JumbledUpLetters!]!) { + user(id: 4) { + name + pets { ... on Badger { name }, ...PetFields } + } + } + fragment PetFields on Peat { + name + } + `, + true, + ).toDeepEqual([ + { + message: 'Unknown type "JumbledUpLetters".', + locations: [{ line: 2, column: 24 }], + }, + { + message: 'Unknown type "Badger".', + locations: [{ line: 5, column: 25 }], + }, + { + message: 'Unknown type "Peat".', + locations: [{ line: 8, column: 29 }], + }, + ]); + }); + it('references to standard scalars that are missing in schema', () => { const schema = buildSchema('type Query { foo: String }'); const query = ` diff --git a/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts b/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts index 819d103e6ae..11d82de9c39 100644 --- a/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts +++ b/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts @@ -19,8 +19,12 @@ import { expectValidationErrorsWithSchema, } from './harness.js'; -function expectErrors(queryStr: string) { - return expectValidationErrors(ValuesOfCorrectTypeRule, queryStr); +function expectErrors(queryStr: string, maskSuggestions = false) { + return expectValidationErrors( + ValuesOfCorrectTypeRule, + queryStr, + maskSuggestions, + ); } function expectErrorsWithSchema(schema: GraphQLSchema, queryStr: string) { @@ -526,6 +530,24 @@ describe('Validate: Values of correct type', () => { ]); }); + it('String into Enum (no suggestion)', () => { + expectErrors( + ` + { + dog { + doesKnowCommand(dogCommand: "SIT") + } + } + `, + true, + ).toDeepEqual([ + { + message: 'Enum "DogCommand" cannot represent non-enum value: "SIT".', + locations: [{ line: 4, column: 41 }], + }, + ]); + }); + it('Boolean into Enum', () => { expectErrors(` { @@ -571,6 +593,24 @@ describe('Validate: Values of correct type', () => { }, ]); }); + + it('Different case Enum Value into Enum (no suggestion)', () => { + expectErrors( + ` + { + dog { + doesKnowCommand(dogCommand: sit) + } + } + `, + true, + ).toDeepEqual([ + { + message: 'Value "sit" does not exist in "DogCommand" enum.', + locations: [{ line: 4, column: 41 }], + }, + ]); + }); }); describe('Valid List value', () => { @@ -968,6 +1008,28 @@ describe('Validate: Values of correct type', () => { ]); }); + it('Partial object, unknown field arg (no suggestions)', () => { + expectErrors( + ` + { + complicatedArgs { + complexArgField(complexArg: { + requiredField: true, + invalidField: "value" + }) + } + } + `, + true, + ).toDeepEqual([ + { + message: + 'Field "invalidField" is not defined by type "ComplexInput".', + locations: [{ line: 6, column: 15 }], + }, + ]); + }); + it('reports original error for custom scalar which throws', () => { const customScalar = new GraphQLScalarType({ name: 'Invalid', diff --git a/src/validation/__tests__/harness.ts b/src/validation/__tests__/harness.ts index 0db861f45b0..3e47b43cb61 100644 --- a/src/validation/__tests__/harness.ts +++ b/src/validation/__tests__/harness.ts @@ -128,17 +128,24 @@ export function expectValidationErrorsWithSchema( schema: GraphQLSchema, rule: ValidationRule, queryStr: string, + maskSuggestions = false, ): any { const doc = parse(queryStr, { experimentalFragmentArguments: true }); - const errors = validate(schema, doc, [rule]); + const errors = validate(schema, doc, [rule], { maskSuggestions }); return expectJSON(errors); } export function expectValidationErrors( rule: ValidationRule, queryStr: string, + maskSuggestions = false, ): any { - return expectValidationErrorsWithSchema(testSchema, rule, queryStr); + return expectValidationErrorsWithSchema( + testSchema, + rule, + queryStr, + maskSuggestions, + ); } export function expectSDLValidationErrors( diff --git a/src/validation/rules/FieldsOnCorrectTypeRule.ts b/src/validation/rules/FieldsOnCorrectTypeRule.ts index ba7898fa3de..3637d5cfd2a 100644 --- a/src/validation/rules/FieldsOnCorrectTypeRule.ts +++ b/src/validation/rules/FieldsOnCorrectTypeRule.ts @@ -45,17 +45,17 @@ export function FieldsOnCorrectTypeRule( // First determine if there are any suggested types to condition on. let suggestion = didYouMean( 'to use an inline fragment on', - context.shouldProvideSuggestions - ? getSuggestedTypeNames(schema, type, fieldName) - : [], + context.maskSuggestions + ? [] + : getSuggestedTypeNames(schema, type, fieldName), ); // If there are no suggested types, then perhaps this was a typo? if (suggestion === '') { suggestion = didYouMean( - context.shouldProvideSuggestions - ? getSuggestedFieldNames(type, fieldName) - : [], + context.maskSuggestions + ? [] + : getSuggestedFieldNames(type, fieldName), ); } diff --git a/src/validation/rules/KnownArgumentNamesRule.ts b/src/validation/rules/KnownArgumentNamesRule.ts index cbff2efa218..258899fdb4d 100644 --- a/src/validation/rules/KnownArgumentNamesRule.ts +++ b/src/validation/rules/KnownArgumentNamesRule.ts @@ -34,14 +34,14 @@ export function KnownArgumentNamesRule(context: ValidationContext): ASTVisitor { ); if (!varDef) { const argName = argNode.name.value; - const suggestions = context.shouldProvideSuggestions - ? suggestionList( + const suggestions = context.maskSuggestions + ? [] + : suggestionList( argName, Array.from(fragmentSignature.variableDefinitions.values()).map( (varSignature) => varSignature.variable.name.value, ), - ) - : []; + ); context.reportError( new GraphQLError( `Unknown argument "${argName}" on fragment "${fragmentSignature.definition.name.value}".` + @@ -59,12 +59,12 @@ export function KnownArgumentNamesRule(context: ValidationContext): ASTVisitor { if (!argDef && fieldDef && parentType) { const argName = argNode.name.value; - const suggestions = context.shouldProvideSuggestions - ? suggestionList( + const suggestions = context.maskSuggestions + ? [] + : suggestionList( argName, fieldDef.args.map((arg) => arg.name), - ) - : []; + ); context.reportError( new GraphQLError( `Unknown argument "${argName}" on field "${parentType}.${fieldDef.name}".` + @@ -123,7 +123,7 @@ export function KnownArgumentNamesOnDirectivesRule( context.reportError( new GraphQLError( `Unknown argument "${argName}" on directive "@${directiveName}".` + - didYouMean(suggestions), + (context.maskSuggestions ? '' : didYouMean(suggestions)), { nodes: argNode }, ), ); diff --git a/src/validation/rules/KnownTypeNamesRule.ts b/src/validation/rules/KnownTypeNamesRule.ts index 8a4e7d37050..516d3297b26 100644 --- a/src/validation/rules/KnownTypeNamesRule.ts +++ b/src/validation/rules/KnownTypeNamesRule.ts @@ -48,12 +48,12 @@ export function KnownTypeNamesRule( return; } - const suggestedTypes = context.shouldProvideSuggestions - ? suggestionList( + const suggestedTypes = context.maskSuggestions + ? [] + : suggestionList( typeName, isSDL ? [...standardTypeNames, ...typeNames] : [...typeNames], - ) - : []; + ); context.reportError( new GraphQLError( `Unknown type "${typeName}".` + didYouMean(suggestedTypes), diff --git a/src/validation/rules/PossibleTypeExtensionsRule.ts b/src/validation/rules/PossibleTypeExtensionsRule.ts index 58ab981544f..57f2b5541b5 100644 --- a/src/validation/rules/PossibleTypeExtensionsRule.ts +++ b/src/validation/rules/PossibleTypeExtensionsRule.ts @@ -78,13 +78,10 @@ export function PossibleTypeExtensionsRule( ...Object.keys(schema?.getTypeMap() ?? {}), ]; - const suggestedTypes = context.shouldProvideSuggestions - ? suggestionList(typeName, allTypeNames) - : []; context.reportError( new GraphQLError( `Cannot extend type "${typeName}" because it is not defined.` + - didYouMean(suggestedTypes), + didYouMean(suggestionList(typeName, allTypeNames)), { nodes: node.name }, ), ); diff --git a/src/validation/rules/SingleFieldSubscriptionsRule.ts b/src/validation/rules/SingleFieldSubscriptionsRule.ts index 2322ca4fdb4..80a69800588 100644 --- a/src/validation/rules/SingleFieldSubscriptionsRule.ts +++ b/src/validation/rules/SingleFieldSubscriptionsRule.ts @@ -51,6 +51,7 @@ export function SingleFieldSubscriptionsRule( variableValues, subscriptionType, node, + context.maskSuggestions, ); if (groupedFieldSet.size > 1) { const fieldDetailsLists = [...groupedFieldSet.values()]; diff --git a/src/validation/rules/ValuesOfCorrectTypeRule.ts b/src/validation/rules/ValuesOfCorrectTypeRule.ts index 026a172408a..4095bb2bf83 100644 --- a/src/validation/rules/ValuesOfCorrectTypeRule.ts +++ b/src/validation/rules/ValuesOfCorrectTypeRule.ts @@ -97,9 +97,12 @@ export function ValuesOfCorrectTypeRule( const parentType = getNamedType(context.getParentInputType()); const fieldType = context.getInputType(); if (!fieldType && isInputObjectType(parentType)) { - const suggestions = context.shouldProvideSuggestions - ? suggestionList(node.name.value, Object.keys(parentType.getFields())) - : []; + const suggestions = context.maskSuggestions + ? [] + : suggestionList( + node.name.value, + Object.keys(parentType.getFields()), + ); context.reportError( new GraphQLError( `Field "${node.name.value}" is not defined by type "${parentType}".` + @@ -156,11 +159,8 @@ function isValidValueNode(context: ValidationContext, node: ValueNode): void { // which may throw or return undefined to indicate an invalid value. try { const parseResult = type.parseConstLiteral - ? type.parseConstLiteral( - replaceVariables(node), - context.shouldProvideSuggestions, - ) - : type.parseLiteral(node, undefined, context.shouldProvideSuggestions); + ? type.parseConstLiteral(replaceVariables(node), context.maskSuggestions) + : type.parseLiteral(node, undefined, context.maskSuggestions); if (parseResult === undefined) { const typeStr = inspect(locationType); context.reportError( diff --git a/src/validation/validate.ts b/src/validation/validate.ts index 6a5da134846..00d5e7748c6 100644 --- a/src/validation/validate.ts +++ b/src/validation/validate.ts @@ -41,10 +41,10 @@ export function validate( schema: GraphQLSchema, documentAST: DocumentNode, rules: ReadonlyArray = specifiedRules, - options?: { maxErrors?: number; shouldProvideSuggestions?: boolean }, + options?: { maxErrors?: number; maskSuggestions?: Maybe }, ): ReadonlyArray { const maxErrors = options?.maxErrors ?? 100; - const shouldProvideSuggestions = options?.shouldProvideSuggestions ?? true; + const maskSuggestions = options?.maskSuggestions ?? false; // If the schema used for validation is invalid, throw an error. assertValidSchema(schema); @@ -64,7 +64,7 @@ export function validate( } errors.push(error); }, - shouldProvideSuggestions, + maskSuggestions, ); // This uses a specialized visitor which runs multiple visitors in parallel, From 644a676b993c3943440cc02128d0c2c6417e06e3 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Mon, 7 Oct 2024 22:42:17 +0300 Subject: [PATCH 3/4] suggestions --- src/execution/collectFields.ts | 24 +++++++++++++++---- src/graphql.ts | 2 +- .../__tests__/coerceInputValue-test.ts | 2 +- .../rules/PossibleTypeExtensionsRule.ts | 3 ++- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/execution/collectFields.ts b/src/execution/collectFields.ts index 65d1cb48e00..db9f24e1840 100644 --- a/src/execution/collectFields.ts +++ b/src/execution/collectFields.ts @@ -54,8 +54,8 @@ interface CollectFieldsContext { variableValues: VariableValues; operation: OperationDefinitionNode; runtimeType: GraphQLObjectType; - visitedFragmentNames: Set; maskSuggestions: boolean; + visitedFragmentNames: Set; } /** @@ -87,8 +87,8 @@ export function collectFields( variableValues, runtimeType, operation, - visitedFragmentNames: new Set(), maskSuggestions, + visitedFragmentNames: new Set(), }; collectFieldsImpl( @@ -171,6 +171,7 @@ function collectFieldsImpl( variableValues, runtimeType, operation, + maskSuggestions, visitedFragmentNames, } = context; @@ -178,7 +179,12 @@ function collectFieldsImpl( switch (selection.kind) { case Kind.FIELD: { if ( - !shouldIncludeNode(selection, variableValues, fragmentVariableValues) + !shouldIncludeNode( + selection, + variableValues, + fragmentVariableValues, + maskSuggestions, + ) ) { continue; } @@ -195,6 +201,7 @@ function collectFieldsImpl( selection, variableValues, fragmentVariableValues, + maskSuggestions, ) || !doesFragmentConditionMatch(schema, selection, runtimeType) ) { @@ -207,6 +214,7 @@ function collectFieldsImpl( fragmentVariableValues, selection, deferUsage, + maskSuggestions, ); if (!newDeferUsage) { @@ -241,6 +249,7 @@ function collectFieldsImpl( fragmentVariableValues, selection, deferUsage, + maskSuggestions, ); if ( @@ -250,6 +259,7 @@ function collectFieldsImpl( selection, variableValues, fragmentVariableValues, + maskSuggestions, )) ) { continue; @@ -271,7 +281,7 @@ function collectFieldsImpl( fragmentVariableSignatures, variableValues, fragmentVariableValues, - false, + maskSuggestions, ); } @@ -307,18 +317,21 @@ function collectFieldsImpl( * deferred based on the experimental flag, defer directive present and * not disabled by the "if" argument. */ +// eslint-disable-next-line @typescript-eslint/max-params function getDeferUsage( operation: OperationDefinitionNode, variableValues: VariableValues, fragmentVariableValues: VariableValues | undefined, node: FragmentSpreadNode | InlineFragmentNode, parentDeferUsage: DeferUsage | undefined, + maskSuggestions: boolean, ): DeferUsage | undefined { const defer = getDirectiveValues( GraphQLDeferDirective, node, variableValues, fragmentVariableValues, + maskSuggestions, ); if (!defer) { @@ -348,12 +361,14 @@ function shouldIncludeNode( node: FragmentSpreadNode | FieldNode | InlineFragmentNode, variableValues: VariableValues, fragmentVariableValues: VariableValues | undefined, + maskSuggestions: boolean, ): boolean { const skip = getDirectiveValues( GraphQLSkipDirective, node, variableValues, fragmentVariableValues, + maskSuggestions, ); if (skip?.if === true) { return false; @@ -364,6 +379,7 @@ function shouldIncludeNode( node, variableValues, fragmentVariableValues, + maskSuggestions, ); if (include?.if === false) { return false; diff --git a/src/graphql.ts b/src/graphql.ts index 376798e783d..5ede88321b2 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -61,13 +61,13 @@ import type { ExecutionResult } from './execution/types.js'; export interface GraphQLArgs { schema: GraphQLSchema; source: string | Source; - maskSuggestions?: boolean; rootValue?: unknown; contextValue?: unknown; variableValues?: Maybe<{ readonly [variable: string]: unknown }>; operationName?: Maybe; fieldResolver?: Maybe>; typeResolver?: Maybe>; + maskSuggestions?: Maybe; } export function graphql(args: GraphQLArgs): Promise { diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index 3ba8b3b8347..17f4e540e97 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -50,7 +50,7 @@ interface CoerceError { function coerceValue( inputValue: unknown, type: GraphQLInputType, - maskSuggestions: boolean = false, + maskSuggestions = false, ): CoerceResult { const errors: Array = []; const value = coerceInputValue( diff --git a/src/validation/rules/PossibleTypeExtensionsRule.ts b/src/validation/rules/PossibleTypeExtensionsRule.ts index 57f2b5541b5..d9ccb73cfa8 100644 --- a/src/validation/rules/PossibleTypeExtensionsRule.ts +++ b/src/validation/rules/PossibleTypeExtensionsRule.ts @@ -78,10 +78,11 @@ export function PossibleTypeExtensionsRule( ...Object.keys(schema?.getTypeMap() ?? {}), ]; + const suggestedTypes = suggestionList(typeName, allTypeNames); context.reportError( new GraphQLError( `Cannot extend type "${typeName}" because it is not defined.` + - didYouMean(suggestionList(typeName, allTypeNames)), + didYouMean(suggestedTypes), { nodes: node.name }, ), ); From 268f8fc9fc811ee5876a875bced6a1c7746f8e25 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 8 Oct 2024 10:22:14 +0300 Subject: [PATCH 4/4] gfsdg --- src/execution/collectFields.ts | 22 ++++------------ src/execution/execute.ts | 8 +++--- src/execution/values.ts | 26 +++++++++---------- src/graphql.ts | 4 +-- src/type/__tests__/enumType-test.ts | 2 +- src/type/definition.ts | 4 +-- .../__tests__/coerceInputValue-test.ts | 15 +++-------- src/utilities/coerceInputValue.ts | 26 +++++++------------ src/validation/ValidationContext.ts | 2 +- .../__tests__/FieldsOnCorrectTypeRule-test.ts | 2 +- .../__tests__/KnownArgumentNamesRule-test.ts | 2 +- .../__tests__/KnownTypeNamesRule-test.ts | 2 +- .../__tests__/ValuesOfCorrectTypeRule-test.ts | 2 +- src/validation/__tests__/harness.ts | 2 +- src/validation/validate.ts | 2 +- 15 files changed, 48 insertions(+), 73 deletions(-) diff --git a/src/execution/collectFields.ts b/src/execution/collectFields.ts index db9f24e1840..323b3181d71 100644 --- a/src/execution/collectFields.ts +++ b/src/execution/collectFields.ts @@ -179,12 +179,7 @@ function collectFieldsImpl( switch (selection.kind) { case Kind.FIELD: { if ( - !shouldIncludeNode( - selection, - variableValues, - fragmentVariableValues, - maskSuggestions, - ) + !shouldIncludeNode(selection, variableValues, fragmentVariableValues) ) { continue; } @@ -201,7 +196,6 @@ function collectFieldsImpl( selection, variableValues, fragmentVariableValues, - maskSuggestions, ) || !doesFragmentConditionMatch(schema, selection, runtimeType) ) { @@ -214,7 +208,6 @@ function collectFieldsImpl( fragmentVariableValues, selection, deferUsage, - maskSuggestions, ); if (!newDeferUsage) { @@ -249,7 +242,6 @@ function collectFieldsImpl( fragmentVariableValues, selection, deferUsage, - maskSuggestions, ); if ( @@ -259,7 +251,6 @@ function collectFieldsImpl( selection, variableValues, fragmentVariableValues, - maskSuggestions, )) ) { continue; @@ -279,9 +270,9 @@ function collectFieldsImpl( newFragmentVariableValues = getFragmentVariableValues( selection, fragmentVariableSignatures, + maskSuggestions, variableValues, fragmentVariableValues, - maskSuggestions, ); } @@ -317,21 +308,19 @@ function collectFieldsImpl( * deferred based on the experimental flag, defer directive present and * not disabled by the "if" argument. */ -// eslint-disable-next-line @typescript-eslint/max-params function getDeferUsage( operation: OperationDefinitionNode, variableValues: VariableValues, fragmentVariableValues: VariableValues | undefined, node: FragmentSpreadNode | InlineFragmentNode, parentDeferUsage: DeferUsage | undefined, - maskSuggestions: boolean, ): DeferUsage | undefined { const defer = getDirectiveValues( GraphQLDeferDirective, node, + false, variableValues, fragmentVariableValues, - maskSuggestions, ); if (!defer) { @@ -361,14 +350,13 @@ function shouldIncludeNode( node: FragmentSpreadNode | FieldNode | InlineFragmentNode, variableValues: VariableValues, fragmentVariableValues: VariableValues | undefined, - maskSuggestions: boolean, ): boolean { const skip = getDirectiveValues( GraphQLSkipDirective, node, + false, variableValues, fragmentVariableValues, - maskSuggestions, ); if (skip?.if === true) { return false; @@ -377,9 +365,9 @@ function shouldIncludeNode( const include = getDirectiveValues( GraphQLIncludeDirective, node, + false, variableValues, fragmentVariableValues, - maskSuggestions, ); if (include?.if === false) { return false; diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 2b6705dc4d5..7d393fdc9a2 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -804,9 +804,9 @@ function executeField( const args = experimentalGetArgumentValues( fieldDetailsList[0].node, fieldDef.args, + maskSuggestions, variableValues, fieldDetailsList[0].fragmentVariableValues, - maskSuggestions, ); // The resolve function's optional third argument is a context value that @@ -1110,15 +1110,15 @@ function getStreamUsage( ._streamUsage; } - const { operation, variableValues, maskSuggestions } = validatedExecutionArgs; + const { operation, variableValues } = validatedExecutionArgs; // validation only allows equivalent streams on multiple fields, so it is // safe to only check the first fieldNode for the stream directive const stream = getDirectiveValues( GraphQLStreamDirective, fieldDetailsList[0].node, + false, variableValues, fieldDetailsList[0].fragmentVariableValues, - maskSuggestions, ); if (!stream) { @@ -2137,8 +2137,8 @@ function executeSubscription( const args = getArgumentValues( fieldDef, fieldNodes[0], - variableValues, maskSuggestions, + variableValues, ); // Call the `subscribe()` resolver or the default resolver to produce an diff --git a/src/execution/values.ts b/src/execution/values.ts index d7db674867f..84c88bedadc 100644 --- a/src/execution/values.ts +++ b/src/execution/values.ts @@ -59,6 +59,7 @@ export function getVariableValues( ): VariableValuesOrErrors { const errors: Array = []; const maxErrors = options?.maxErrors; + const maskSuggestions = options?.maskSuggestions; try { const variableValues = coerceVariableValues( schema, @@ -72,7 +73,7 @@ export function getVariableValues( } errors.push(error); }, - options?.maskSuggestions, + maskSuggestions, ); if (errors.length === 0) { @@ -90,7 +91,7 @@ function coerceVariableValues( varDefNodes: ReadonlyArray, inputs: { readonly [variable: string]: unknown }, onError: (error: GraphQLError) => void, - maskSuggestions?: Maybe, + maskSuggestions: boolean | undefined, ): VariableValues { const sources: ObjMap = Object.create(null); const coerced: ObjMap = Object.create(null); @@ -142,6 +143,7 @@ function coerceVariableValues( coerced[varName] = coerceInputValue( value, varType, + maskSuggestions, (path, invalidValue, error) => { let prefix = `Variable "$${varName}" got invalid value ` + inspect(invalidValue); @@ -155,7 +157,6 @@ function coerceVariableValues( }), ); }, - maskSuggestions, ); } @@ -165,9 +166,9 @@ function coerceVariableValues( export function getFragmentVariableValues( fragmentSpreadNode: FragmentSpreadNode, fragmentSignatures: ReadOnlyObjMap, + maskSuggestions: boolean, variableValues: VariableValues, fragmentVariableValues?: Maybe, - maskSuggestions?: Maybe, ): VariableValues { const varSignatures: Array = []; const sources = Object.create(null); @@ -184,9 +185,9 @@ export function getFragmentVariableValues( const coerced = experimentalGetArgumentValues( fragmentSpreadNode, varSignatures, + maskSuggestions, variableValues, fragmentVariableValues, - maskSuggestions, ); return { sources, coerced }; @@ -203,24 +204,23 @@ export function getFragmentVariableValues( export function getArgumentValues( def: GraphQLField | GraphQLDirective, node: FieldNode | DirectiveNode, + maskSuggestions?: boolean | undefined, variableValues?: Maybe, - maskSuggestions?: Maybe, ): { [argument: string]: unknown } { return experimentalGetArgumentValues( node, def.args, - variableValues, - undefined, maskSuggestions, + variableValues, ); } export function experimentalGetArgumentValues( node: FieldNode | DirectiveNode | FragmentSpreadNode, argDefs: ReadonlyArray, - variableValues: Maybe, + maskSuggestions?: boolean | undefined, + variableValues?: Maybe, fragmentVariablesValues?: Maybe, - maskSuggestions?: Maybe, ): { [argument: string]: unknown } { const coercedValues: { [argument: string]: unknown } = {}; @@ -294,9 +294,9 @@ export function experimentalGetArgumentValues( const coercedValue = coerceInputLiteral( valueNode, argType, + maskSuggestions, variableValues, fragmentVariablesValues, - maskSuggestions, ); if (coercedValue === undefined) { // Note: ValuesOfCorrectTypeRule validation should catch this before @@ -328,9 +328,9 @@ export function experimentalGetArgumentValues( export function getDirectiveValues( directiveDef: GraphQLDirective, node: { readonly directives?: ReadonlyArray | undefined }, + maskSuggestions?: boolean | undefined, variableValues?: Maybe, fragmentVariableValues?: Maybe, - maskSuggestions?: Maybe, ): undefined | { [argument: string]: unknown } { const directiveNode = node.directives?.find( (directive) => directive.name.value === directiveDef.name, @@ -340,9 +340,9 @@ export function getDirectiveValues( return experimentalGetArgumentValues( directiveNode, directiveDef.args, + maskSuggestions, variableValues, fragmentVariableValues, - maskSuggestions, ); } } diff --git a/src/graphql.ts b/src/graphql.ts index 5ede88321b2..7fcda20e05b 100644 --- a/src/graphql.ts +++ b/src/graphql.ts @@ -67,7 +67,7 @@ export interface GraphQLArgs { operationName?: Maybe; fieldResolver?: Maybe>; typeResolver?: Maybe>; - maskSuggestions?: Maybe; + maskSuggestions?: boolean; } export function graphql(args: GraphQLArgs): Promise { @@ -121,7 +121,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue { // Validate const validationErrors = validate(schema, document, undefined, { - maskSuggestions, + maskSuggestions: maskSuggestions ?? false, }); if (validationErrors.length > 0) { return { errors: validationErrors }; diff --git a/src/type/__tests__/enumType-test.ts b/src/type/__tests__/enumType-test.ts index da5c496e05f..9f0671c12de 100644 --- a/src/type/__tests__/enumType-test.ts +++ b/src/type/__tests__/enumType-test.ts @@ -135,7 +135,7 @@ const schema = new GraphQLSchema({ function executeQuery( source: string, variableValues?: { readonly [variable: string]: unknown }, - maskSuggestions = false, + maskSuggestions: boolean = false, ) { return graphqlSync({ schema, diff --git a/src/type/definition.ts b/src/type/definition.ts index 04b1f338d30..ed3a78366cf 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -1424,7 +1424,7 @@ export class GraphQLEnumType /* */ { parseValue( inputValue: unknown, - maskSuggestions?: Maybe, + maskSuggestions?: boolean | undefined, ): Maybe /* T */ { if (typeof inputValue !== 'string') { const valueStr = inspect(inputValue); @@ -1448,7 +1448,7 @@ export class GraphQLEnumType /* */ { parseLiteral( valueNode: ValueNode, _variables: Maybe>, - maskSuggestions?: Maybe, + maskSuggestions?: boolean | undefined, ): Maybe /* T */ { // Note: variables will be resolved to a value before calling this function. return this.parseConstLiteral(valueNode as ConstValueNode, maskSuggestions); diff --git a/src/utilities/__tests__/coerceInputValue-test.ts b/src/utilities/__tests__/coerceInputValue-test.ts index 17f4e540e97..92cad3f1985 100644 --- a/src/utilities/__tests__/coerceInputValue-test.ts +++ b/src/utilities/__tests__/coerceInputValue-test.ts @@ -50,16 +50,16 @@ interface CoerceError { function coerceValue( inputValue: unknown, type: GraphQLInputType, - maskSuggestions = false, + maskSuggestions: boolean = false, ): CoerceResult { const errors: Array = []; const value = coerceInputValue( inputValue, type, + maskSuggestions, (path, invalidValue, error) => { errors.push({ path, value: invalidValue, error: error.message }); }, - maskSuggestions, ); return { errors, value }; @@ -589,7 +589,7 @@ describe('coerceInputValue', () => { describe('with default onError', () => { it('throw error without path', () => { expect(() => - coerceInputValue(null, new GraphQLNonNull(GraphQLInt), undefined, true), + coerceInputValue(null, new GraphQLNonNull(GraphQLInt), true), ).to.throw( 'Invalid value null: Expected non-nullable type "Int!" not to be null.', ); @@ -600,7 +600,6 @@ describe('coerceInputValue', () => { coerceInputValue( [null], new GraphQLList(new GraphQLNonNull(GraphQLInt)), - undefined, true, ), ).to.throw( @@ -618,13 +617,7 @@ describe('coerceInputLiteral', () => { variableValues?: VariableValues, ) { const ast = parseValue(valueText); - const value = coerceInputLiteral( - ast, - type, - variableValues, - undefined, - true, - ); + const value = coerceInputLiteral(ast, type, true, variableValues); expect(value).to.deep.equal(expected); } diff --git a/src/utilities/coerceInputValue.ts b/src/utilities/coerceInputValue.ts index e94c3d1f557..a748a80dfda 100644 --- a/src/utilities/coerceInputValue.ts +++ b/src/utilities/coerceInputValue.ts @@ -43,15 +43,15 @@ type OnErrorCB = ( export function coerceInputValue( inputValue: unknown, type: GraphQLInputType, + maskSuggestions?: boolean, onError: OnErrorCB = defaultOnError, - maskSuggestions?: Maybe, ): unknown { return coerceInputValueImpl( inputValue, type, onError, undefined, - maskSuggestions, + maskSuggestions ?? false, ); } @@ -73,7 +73,7 @@ function coerceInputValueImpl( type: GraphQLInputType, onError: OnErrorCB, path: Path | undefined, - maskSuggestions?: Maybe, + maskSuggestions: boolean, ): unknown { if (isNonNullType(type)) { if (inputValue != null) { @@ -260,9 +260,9 @@ function coerceInputValueImpl( export function coerceInputLiteral( valueNode: ValueNode, type: GraphQLInputType, + maskSuggestions?: boolean, variableValues?: Maybe, fragmentVariableValues?: Maybe, - maskSuggestions?: Maybe, ): unknown { if (valueNode.kind === Kind.VARIABLE) { const coercedVariableValue = getCoercedVariableValue( @@ -285,9 +285,9 @@ export function coerceInputLiteral( return coerceInputLiteral( valueNode, type.ofType, + maskSuggestions, variableValues, fragmentVariableValues, - maskSuggestions, ); } @@ -301,9 +301,9 @@ export function coerceInputLiteral( const itemValue = coerceInputLiteral( valueNode, type.ofType, + maskSuggestions, variableValues, fragmentVariableValues, - maskSuggestions, ); if (itemValue === undefined) { return; // Invalid: intentionally return no value. @@ -315,9 +315,9 @@ export function coerceInputLiteral( let itemValue = coerceInputLiteral( itemNode, type.ofType, + maskSuggestions, variableValues, fragmentVariableValues, - maskSuggestions, ); if (itemValue === undefined) { if ( @@ -381,9 +381,9 @@ export function coerceInputLiteral( const fieldValue = coerceInputLiteral( fieldNode.value, field.type, + maskSuggestions, variableValues, fragmentVariableValues, - maskSuggestions, ); if (fieldValue === undefined) { return; // Invalid: intentionally return no value. @@ -443,19 +443,13 @@ function getCoercedVariableValue( export function coerceDefaultValue( defaultValue: GraphQLDefaultValueUsage, type: GraphQLInputType, - maskSuggestions?: Maybe, + maskSuggestions?: boolean, ): unknown { // Memoize the result of coercing the default value in a hidden field. let coercedValue = (defaultValue as any)._memoizedCoercedValue; if (coercedValue === undefined) { coercedValue = defaultValue.literal - ? coerceInputLiteral( - defaultValue.literal, - type, - undefined, - undefined, - maskSuggestions, - ) + ? coerceInputLiteral(defaultValue.literal, type, maskSuggestions) : defaultValue.value; (defaultValue as any)._memoizedCoercedValue = coercedValue; } diff --git a/src/validation/ValidationContext.ts b/src/validation/ValidationContext.ts index 803aa7365f3..3b057276d2f 100644 --- a/src/validation/ValidationContext.ts +++ b/src/validation/ValidationContext.ts @@ -188,7 +188,7 @@ export class ValidationContext extends ASTValidationContext { ast: DocumentNode, typeInfo: TypeInfo, onError: (error: GraphQLError) => void, - maskSuggestions?: Maybe, + maskSuggestions?: boolean, ) { super(ast, onError); this._schema = schema; diff --git a/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts b/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts index a0461071a4d..838d9b41fe0 100644 --- a/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts +++ b/src/validation/__tests__/FieldsOnCorrectTypeRule-test.ts @@ -12,7 +12,7 @@ import { validate } from '../validate.js'; import { expectValidationErrorsWithSchema } from './harness.js'; -function expectErrors(queryStr: string, maskSuggestions = false) { +function expectErrors(queryStr: string, maskSuggestions: boolean = false) { return expectValidationErrorsWithSchema( testSchema, FieldsOnCorrectTypeRule, diff --git a/src/validation/__tests__/KnownArgumentNamesRule-test.ts b/src/validation/__tests__/KnownArgumentNamesRule-test.ts index 945161ae4a1..32cd8cb6006 100644 --- a/src/validation/__tests__/KnownArgumentNamesRule-test.ts +++ b/src/validation/__tests__/KnownArgumentNamesRule-test.ts @@ -14,7 +14,7 @@ import { expectValidationErrors, } from './harness.js'; -function expectErrors(queryStr: string, maskSuggestions = false) { +function expectErrors(queryStr: string, maskSuggestions: boolean = false) { return expectValidationErrors( KnownArgumentNamesRule, queryStr, diff --git a/src/validation/__tests__/KnownTypeNamesRule-test.ts b/src/validation/__tests__/KnownTypeNamesRule-test.ts index 7213fcf3129..0c7861ed9b1 100644 --- a/src/validation/__tests__/KnownTypeNamesRule-test.ts +++ b/src/validation/__tests__/KnownTypeNamesRule-test.ts @@ -12,7 +12,7 @@ import { expectValidationErrorsWithSchema, } from './harness.js'; -function expectErrors(queryStr: string, maskSuggestions = false) { +function expectErrors(queryStr: string, maskSuggestions: boolean = false) { return expectValidationErrors(KnownTypeNamesRule, queryStr, maskSuggestions); } diff --git a/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts b/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts index 11d82de9c39..9762463017e 100644 --- a/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts +++ b/src/validation/__tests__/ValuesOfCorrectTypeRule-test.ts @@ -19,7 +19,7 @@ import { expectValidationErrorsWithSchema, } from './harness.js'; -function expectErrors(queryStr: string, maskSuggestions = false) { +function expectErrors(queryStr: string, maskSuggestions: boolean = false) { return expectValidationErrors( ValuesOfCorrectTypeRule, queryStr, diff --git a/src/validation/__tests__/harness.ts b/src/validation/__tests__/harness.ts index 3e47b43cb61..9d816670106 100644 --- a/src/validation/__tests__/harness.ts +++ b/src/validation/__tests__/harness.ts @@ -128,7 +128,7 @@ export function expectValidationErrorsWithSchema( schema: GraphQLSchema, rule: ValidationRule, queryStr: string, - maskSuggestions = false, + maskSuggestions: boolean = false, ): any { const doc = parse(queryStr, { experimentalFragmentArguments: true }); const errors = validate(schema, doc, [rule], { maskSuggestions }); diff --git a/src/validation/validate.ts b/src/validation/validate.ts index 00d5e7748c6..a24145b3cb8 100644 --- a/src/validation/validate.ts +++ b/src/validation/validate.ts @@ -41,7 +41,7 @@ export function validate( schema: GraphQLSchema, documentAST: DocumentNode, rules: ReadonlyArray = specifiedRules, - options?: { maxErrors?: number; maskSuggestions?: Maybe }, + options?: { maxErrors?: number; maskSuggestions?: boolean }, ): ReadonlyArray { const maxErrors = options?.maxErrors ?? 100; const maskSuggestions = options?.maskSuggestions ?? false;