Skip to content

Commit

Permalink
feat: Allow skipping suggestions (#4214)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock authored Oct 12, 2024
1 parent ace6727 commit 0ea9241
Show file tree
Hide file tree
Showing 21 changed files with 466 additions and 65 deletions.
8 changes: 8 additions & 0 deletions src/execution/collectFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ interface CollectFieldsContext {
operation: OperationDefinitionNode;
runtimeType: GraphQLObjectType;
visitedFragmentNames: Set<string>;
hideSuggestions: boolean;
}

/**
Expand All @@ -66,12 +67,14 @@ interface CollectFieldsContext {
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/max-params
export function collectFields(
schema: GraphQLSchema,
fragments: ObjMap<FragmentDetails>,
variableValues: VariableValues,
runtimeType: GraphQLObjectType,
operation: OperationDefinitionNode,
hideSuggestions: boolean,
): {
groupedFieldSet: GroupedFieldSet;
newDeferUsages: ReadonlyArray<DeferUsage>;
Expand All @@ -85,6 +88,7 @@ export function collectFields(
runtimeType,
operation,
visitedFragmentNames: new Set(),
hideSuggestions,
};

collectFieldsImpl(
Expand Down Expand Up @@ -114,6 +118,7 @@ export function collectSubfields(
operation: OperationDefinitionNode,
returnType: GraphQLObjectType,
fieldDetailsList: FieldDetailsList,
hideSuggestions: boolean,
): {
groupedFieldSet: GroupedFieldSet;
newDeferUsages: ReadonlyArray<DeferUsage>;
Expand All @@ -125,6 +130,7 @@ export function collectSubfields(
runtimeType: returnType,
operation,
visitedFragmentNames: new Set(),
hideSuggestions,
};
const subGroupedFieldSet = new AccumulatorMap<string, FieldDetails>();
const newDeferUsages: Array<DeferUsage> = [];
Expand Down Expand Up @@ -166,6 +172,7 @@ function collectFieldsImpl(
runtimeType,
operation,
visitedFragmentNames,
hideSuggestions,
} = context;

for (const selection of selectionSet.selections) {
Expand Down Expand Up @@ -265,6 +272,7 @@ function collectFieldsImpl(
fragmentVariableSignatures,
variableValues,
fragmentVariableValues,
hideSuggestions,
);
}

Expand Down
36 changes: 30 additions & 6 deletions src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const collectSubfields = memoize3(
returnType: GraphQLObjectType,
fieldDetailsList: FieldDetailsList,
) => {
const { schema, fragments, operation, variableValues } =
const { schema, fragments, operation, variableValues, hideSuggestions } =
validatedExecutionArgs;
return _collectSubfields(
schema,
Expand All @@ -107,6 +107,7 @@ const collectSubfields = memoize3(
operation,
returnType,
fieldDetailsList,
hideSuggestions,
);
},
);
Expand Down Expand Up @@ -155,6 +156,7 @@ export interface ValidatedExecutionArgs {
validatedExecutionArgs: ValidatedExecutionArgs,
) => PromiseOrValue<ExecutionResult>;
enableEarlyExecution: boolean;
hideSuggestions: boolean;
}

export interface ExecutionContext {
Expand Down Expand Up @@ -184,6 +186,7 @@ export interface ExecutionArgs {
) => PromiseOrValue<ExecutionResult>
>;
enableEarlyExecution?: Maybe<boolean>;
hideSuggestions?: Maybe<boolean>;
}

export interface StreamUsage {
Expand Down Expand Up @@ -308,8 +311,14 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent(
cancellableStreams: undefined,
};
try {
const { schema, fragments, rootValue, operation, variableValues } =
validatedExecutionArgs;
const {
schema,
fragments,
rootValue,
operation,
variableValues,
hideSuggestions,
} = validatedExecutionArgs;
const rootType = schema.getRootType(operation.operation);
if (rootType == null) {
throw new GraphQLError(
Expand All @@ -324,6 +333,7 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent(
variableValues,
rootType,
operation,
hideSuggestions,
);

const { groupedFieldSet, newDeferUsages } = collectedFields;
Expand Down Expand Up @@ -554,12 +564,16 @@ export function validateExecutionArgs(
// FIXME: https://github.com/graphql/graphql-js/issues/2203
/* c8 ignore next */
const variableDefinitions = operation.variableDefinitions ?? [];
const hideSuggestions = args.hideSuggestions ?? false;

const variableValuesOrErrors = getVariableValues(
schema,
variableDefinitions,
rawVariableValues ?? {},
{ maxErrors: 50 },
{
maxErrors: 50,
hideSuggestions,
},
);

if (variableValuesOrErrors.errors) {
Expand All @@ -579,6 +593,7 @@ export function validateExecutionArgs(
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
perEventExecutor: perEventExecutor ?? executeSubscriptionEvent,
enableEarlyExecution: enableEarlyExecution === true,
hideSuggestions,
};
}

Expand Down Expand Up @@ -762,7 +777,8 @@ function executeField(
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLWrappedResult<unknown>> | undefined {
const validatedExecutionArgs = exeContext.validatedExecutionArgs;
const { schema, contextValue, variableValues } = validatedExecutionArgs;
const { schema, contextValue, variableValues, hideSuggestions } =
validatedExecutionArgs;
const fieldName = fieldDetailsList[0].node.name.value;
const fieldDef = schema.getField(parentType, fieldName);
if (!fieldDef) {
Expand Down Expand Up @@ -790,6 +806,7 @@ function executeField(
fieldDef.args,
variableValues,
fieldDetailsList[0].fragmentVariableValues,
hideSuggestions,
);

// The resolve function's optional third argument is a context value that
Expand Down Expand Up @@ -2065,6 +2082,7 @@ function executeSubscription(
contextValue,
operation,
variableValues,
hideSuggestions,
} = validatedExecutionArgs;

const rootType = schema.getSubscriptionType();
Expand All @@ -2081,6 +2099,7 @@ function executeSubscription(
variableValues,
rootType,
operation,
hideSuggestions,
);

const firstRootField = groupedFieldSet.entries().next().value as [
Expand Down Expand Up @@ -2114,7 +2133,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],
variableValues,
hideSuggestions,
);

// Call the `subscribe()` resolver or the default resolver to produce an
// AsyncIterable yielding raw payloads.
Expand Down
28 changes: 25 additions & 3 deletions src/execution/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function getVariableValues(
schema: GraphQLSchema,
varDefNodes: ReadonlyArray<VariableDefinitionNode>,
inputs: { readonly [variable: string]: unknown },
options?: { maxErrors?: number },
options?: { maxErrors?: number; hideSuggestions?: boolean },
): VariableValuesOrErrors {
const errors: Array<GraphQLError> = [];
const maxErrors = options?.maxErrors;
Expand All @@ -72,6 +72,7 @@ export function getVariableValues(
}
errors.push(error);
},
options?.hideSuggestions,
);

if (errors.length === 0) {
Expand All @@ -89,6 +90,7 @@ function coerceVariableValues(
varDefNodes: ReadonlyArray<VariableDefinitionNode>,
inputs: { readonly [variable: string]: unknown },
onError: (error: GraphQLError) => void,
hideSuggestions?: Maybe<boolean>,
): VariableValues {
const sources: ObjMap<VariableValueSource> = Object.create(null);
const coerced: ObjMap<unknown> = Object.create(null);
Expand All @@ -105,7 +107,11 @@ function coerceVariableValues(
const defaultValue = varSignature.defaultValue;
if (defaultValue) {
sources[varName] = { signature: varSignature };
coerced[varName] = coerceDefaultValue(defaultValue, varType);
coerced[varName] = coerceDefaultValue(
defaultValue,
varType,
hideSuggestions,
);
} else if (isNonNullType(varType)) {
const varTypeStr = inspect(varType);
onError(
Expand Down Expand Up @@ -149,6 +155,7 @@ function coerceVariableValues(
}),
);
},
hideSuggestions,
);
}

Expand All @@ -160,6 +167,7 @@ export function getFragmentVariableValues(
fragmentSignatures: ReadOnlyObjMap<GraphQLVariableSignature>,
variableValues: VariableValues,
fragmentVariableValues?: Maybe<VariableValues>,
hideSuggestions?: Maybe<boolean>,
): VariableValues {
const varSignatures: Array<GraphQLVariableSignature> = [];
const sources = Object.create(null);
Expand All @@ -178,6 +186,7 @@ export function getFragmentVariableValues(
varSignatures,
variableValues,
fragmentVariableValues,
hideSuggestions,
);

return { sources, coerced };
Expand All @@ -195,15 +204,23 @@ export function getArgumentValues(
def: GraphQLField<unknown, unknown> | GraphQLDirective,
node: FieldNode | DirectiveNode,
variableValues?: Maybe<VariableValues>,
hideSuggestions?: Maybe<boolean>,
): { [argument: string]: unknown } {
return experimentalGetArgumentValues(node, def.args, variableValues);
return experimentalGetArgumentValues(
node,
def.args,
variableValues,
undefined,
hideSuggestions,
);
}

export function experimentalGetArgumentValues(
node: FieldNode | DirectiveNode | FragmentSpreadNode,
argDefs: ReadonlyArray<GraphQLArgument | GraphQLVariableSignature>,
variableValues: Maybe<VariableValues>,
fragmentVariablesValues?: Maybe<VariableValues>,
hideSuggestions?: Maybe<boolean>,
): { [argument: string]: unknown } {
const coercedValues: { [argument: string]: unknown } = {};

Expand All @@ -222,6 +239,7 @@ export function experimentalGetArgumentValues(
coercedValues[name] = coerceDefaultValue(
argDef.defaultValue,
argDef.type,
hideSuggestions,
);
} else if (isNonNullType(argType)) {
throw new GraphQLError(
Expand Down Expand Up @@ -251,6 +269,7 @@ export function experimentalGetArgumentValues(
coercedValues[name] = coerceDefaultValue(
argDef.defaultValue,
argDef.type,
hideSuggestions,
);
} else if (isNonNullType(argType)) {
throw new GraphQLError(
Expand All @@ -277,6 +296,7 @@ export function experimentalGetArgumentValues(
argType,
variableValues,
fragmentVariablesValues,
hideSuggestions,
);
if (coercedValue === undefined) {
// Note: ValuesOfCorrectTypeRule validation should catch this before
Expand Down Expand Up @@ -310,6 +330,7 @@ export function getDirectiveValues(
node: { readonly directives?: ReadonlyArray<DirectiveNode> | undefined },
variableValues?: Maybe<VariableValues>,
fragmentVariableValues?: Maybe<VariableValues>,
hideSuggestions?: Maybe<boolean>,
): undefined | { [argument: string]: unknown } {
const directiveNode = node.directives?.find(
(directive) => directive.name.value === directiveDef.name,
Expand All @@ -321,6 +342,7 @@ export function getDirectiveValues(
directiveDef.args,
variableValues,
fragmentVariableValues,
hideSuggestions,
);
}
}
7 changes: 6 additions & 1 deletion src/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import type { ExecutionResult } from './execution/types.js';
export interface GraphQLArgs {
schema: GraphQLSchema;
source: string | Source;
hideSuggestions?: Maybe<boolean>;
rootValue?: unknown;
contextValue?: unknown;
variableValues?: Maybe<{ readonly [variable: string]: unknown }>;
Expand Down Expand Up @@ -101,6 +102,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
hideSuggestions,
} = args;

// Validate Schema
Expand All @@ -118,7 +120,9 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
}

// Validate
const validationErrors = validate(schema, document);
const validationErrors = validate(schema, document, undefined, {
hideSuggestions,
});
if (validationErrors.length > 0) {
return { errors: validationErrors };
}
Expand All @@ -133,5 +137,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
hideSuggestions,
});
}
25 changes: 24 additions & 1 deletion src/type/__tests__/enumType-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,14 @@ const schema = new GraphQLSchema({
function executeQuery(
source: string,
variableValues?: { readonly [variable: string]: unknown },
hideSuggestions = false,
) {
return graphqlSync({ schema, source, variableValues });
return graphqlSync({
schema,
source,
variableValues,
hideSuggestions,
});
}

describe('Type System: Enum Values', () => {
Expand Down Expand Up @@ -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) }');

Expand Down
Loading

0 comments on commit 0ea9241

Please sign in to comment.