Skip to content

Commit

Permalink
Preserve defaultValue literals
Browse files Browse the repository at this point in the history
Fixes #3051
  • Loading branch information
leebyron committed May 10, 2021
1 parent c853294 commit 29971d0
Show file tree
Hide file tree
Showing 19 changed files with 161 additions and 68 deletions.
15 changes: 11 additions & 4 deletions src/execution/values.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { GraphQLSchema } from '../type/schema';
import type { GraphQLField } from '../type/definition';
import type { GraphQLDirective } from '../type/directives';
import { isInputType, isNonNullType } from '../type/definition';
import { getCoercedDefaultValue } from '../type/defaultValues';

import { typeFromAST } from '../utilities/typeFromAST';
import { valueFromAST } from '../utilities/valueFromAST';
Expand Down Expand Up @@ -173,8 +174,11 @@ export function getArgumentValues(
const argumentNode = argNodeMap[name];

if (!argumentNode) {
if (argDef.defaultValue !== undefined) {
coercedValues[name] = argDef.defaultValue;
if (argDef.defaultValue) {
coercedValues[name] = getCoercedDefaultValue(
argDef.defaultValue,
argDef.type,
);
} else if (isNonNullType(argType)) {
throw new GraphQLError(
`Argument "${name}" of required type "${inspect(argType)}" ` +
Expand All @@ -194,8 +198,11 @@ export function getArgumentValues(
variableValues == null ||
!hasOwnProperty(variableValues, variableName)
) {
if (argDef.defaultValue !== undefined) {
coercedValues[name] = argDef.defaultValue;
if (argDef.defaultValue) {
coercedValues[name] = getCoercedDefaultValue(
argDef.defaultValue,
argDef.type,
);
} else if (isNonNullType(argType)) {
throw new GraphQLError(
`Argument "${name}" of required type "${inspect(argType)}" ` +
Expand Down
4 changes: 4 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ export {
// Validate GraphQL schema.
validateSchema,
assertValidSchema,
// Operate on default values.
getCoercedDefaultValue,
getLiteralDefaultValue,
} from './type/index';

export {
Expand All @@ -150,6 +153,7 @@ export {
GraphQLArgumentConfig,
GraphQLArgumentExtensions,
GraphQLInputValue,
GraphQLDefaultValueUsage,
GraphQLInputValueConfig,
GraphQLEnumTypeConfig,
GraphQLEnumTypeExtensions,
Expand Down
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ export {
// Validate GraphQL schema.
validateSchema,
assertValidSchema,
// Operate on default values.
getCoercedDefaultValue,
getLiteralDefaultValue,
} from './type/index';

export type {
Expand All @@ -146,6 +149,7 @@ export type {
GraphQLArgument,
GraphQLArgumentConfig,
GraphQLInputValue,
GraphQLDefaultValueUsage,
GraphQLInputValueConfig,
GraphQLEnumTypeConfig,
GraphQLEnumValue,
Expand Down
31 changes: 11 additions & 20 deletions src/type/__tests__/predicate-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
assertNamedType,
getNullableType,
getNamedType,
defineInputValue,
} from '../definition';

const ObjectType = new GraphQLObjectType({ name: 'Object', fields: {} });
Expand Down Expand Up @@ -562,19 +563,14 @@ describe('Type predicates', () => {
});

describe('isRequiredInput', () => {
function buildArg(config: {|
function buildArg({
type,
defaultValue,
}: {|
type: GraphQLInputType,
defaultValue?: mixed,
|}): GraphQLArgument {
return {
name: 'someArg',
type: config.type,
description: undefined,
defaultValue: config.defaultValue,
deprecationReason: null,
extensions: undefined,
astNode: undefined,
};
return defineInputValue({ type, defaultValue }, 'someArg');
}

it('returns true for required arguments', () => {
Expand Down Expand Up @@ -608,19 +604,14 @@ describe('Type predicates', () => {
expect(isRequiredInput(optArg4)).to.equal(false);
});

function buildInputField(config: {|
function buildInputField({
type,
defaultValue,
}: {|
type: GraphQLInputType,
defaultValue?: mixed,
|}): GraphQLInputField {
return {
name: 'someInputField',
type: config.type,
description: undefined,
defaultValue: config.defaultValue,
deprecationReason: null,
extensions: undefined,
astNode: undefined,
};
return defineInputValue({ type, defaultValue }, 'someInputField');
}

it('returns true for required input field', () => {
Expand Down
13 changes: 13 additions & 0 deletions src/type/defaultValues.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ConstValueNode } from '../language/ast';

import { GraphQLInputType, GraphQLDefaultValueUsage } from './definition';

export function getLiteralDefaultValue(
usage: GraphQLDefaultValueUsage,
type: GraphQLInputType,
): ConstValueNode;

export function getCoercedDefaultValue(
usage: GraphQLDefaultValueUsage,
type: GraphQLInputType,
): unknown;
28 changes: 28 additions & 0 deletions src/type/defaultValues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { invariant } from '../jsutils/invariant';
import type { ConstValueNode } from '../language/ast';

import { astFromValue } from '../utilities/astFromValue';
import { valueFromAST } from '../utilities/valueFromAST';

import type { GraphQLInputType, GraphQLDefaultValueUsage } from './definition';

export function getLiteralDefaultValue(
usage: GraphQLDefaultValueUsage,
type: GraphQLInputType,
): ConstValueNode {
if (usage.literal) {
return usage.literal;
}
const literal = astFromValue(usage.value, type);
invariant(literal, 'Value cannot be converted to literal for this type');
return literal;
}

export function getCoercedDefaultValue(
usage: GraphQLDefaultValueUsage,
type: GraphQLInputType,
): mixed {
return usage.value !== undefined
? usage.value
: valueFromAST(usage.literal, type);
}
8 changes: 7 additions & 1 deletion src/type/definition.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
FieldNode,
FragmentDefinitionNode,
ValueNode,
ConstValueNode,
ScalarTypeExtensionNode,
UnionTypeExtensionNode,
EnumTypeExtensionNode,
Expand Down Expand Up @@ -575,12 +576,17 @@ export interface GraphQLInputValue<Extensions> {
name: string;
description: Maybe<string>;
type: GraphQLInputType;
defaultValue: unknown;
defaultValue: Maybe<GraphQLDefaultValueUsage>;
deprecationReason: Maybe<string>;
extensions: Maybe<Readonly<Extensions>>;
astNode: Maybe<InputValueDefinitionNode>;
}

export interface GraphQLDefaultValueUsage {
value: unknown;
literal: Maybe<ConstValueNode>;
}

export interface GraphQLInputValueConfig<Extensions> {
description?: Maybe<string>;
type: GraphQLInputType;
Expand Down
21 changes: 18 additions & 3 deletions src/type/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type {
FieldNode,
FragmentDefinitionNode,
ValueNode,
ConstValueNode,
} from '../language/ast';

import { valueFromASTUntyped } from '../utilities/valueFromASTUntyped';
Expand Down Expand Up @@ -971,11 +972,18 @@ export function defineInputValue(
!('resolve' in config),
`${name} has a resolve property, but inputs cannot define resolvers.`,
);
let defaultValue;
if (config.defaultValue !== undefined || config.defaultValueLiteral) {
defaultValue = {
value: config.defaultValue,
literal: config.defaultValueLiteral,
};
}
return {
name,
description: config.description,
type: config.type,
defaultValue: config.defaultValue,
defaultValue,
deprecationReason: config.deprecationReason,
extensions: config.extensions && toObjMap(config.extensions),
astNode: config.astNode,
Expand All @@ -991,7 +999,8 @@ export function inputValueToConfig(
return {
description: inputValue.description,
type: inputValue.type,
defaultValue: inputValue.defaultValue,
defaultValue: inputValue.defaultValue?.value,
defaultValueLiteral: inputValue.defaultValue?.literal,
deprecationReason: inputValue.deprecationReason,
extensions: inputValue.extensions,
astNode: inputValue.astNode,
Expand All @@ -1002,16 +1011,22 @@ export type GraphQLInputValue = {|
name: string,
description: ?string,
type: GraphQLInputType,
defaultValue: mixed,
defaultValue: ?GraphQLDefaultValueUsage,
deprecationReason: ?string,
extensions: ?ReadOnlyObjMap<mixed>,
astNode: ?InputValueDefinitionNode,
|};

export type GraphQLDefaultValueUsage = {|
value: mixed,
literal: ?ConstValueNode,
|};

export type GraphQLInputValueConfig = {|
description?: ?string,
type: GraphQLInputType,
defaultValue?: mixed,
defaultValueLiteral?: ?ConstValueNode,
deprecationReason?: ?string,
extensions?: ?ReadOnlyObjMapLike<mixed>,
astNode?: ?InputValueDefinitionNode,
Expand Down
6 changes: 6 additions & 0 deletions src/type/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export {
GraphQLArgumentConfig,
GraphQLArgumentExtensions,
GraphQLInputValue,
GraphQLDefaultValueUsage,
GraphQLInputValueConfig,
GraphQLEnumTypeConfig,
GraphQLEnumTypeExtensions,
Expand Down Expand Up @@ -117,6 +118,11 @@ export {
GraphQLScalarLiteralParser,
} from './definition';

export {
getCoercedDefaultValue,
getLiteralDefaultValue,
} from './defaultValues';

export {
// Predicate
isDirective,
Expand Down
6 changes: 6 additions & 0 deletions src/type/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export type {
GraphQLArgument,
GraphQLArgumentConfig,
GraphQLInputValue,
GraphQLDefaultValueUsage,
GraphQLInputValueConfig,
GraphQLEnumTypeConfig,
GraphQLEnumValue,
Expand All @@ -162,5 +163,10 @@ export type {
GraphQLScalarLiteralParser,
} from './definition';

export {
getCoercedDefaultValue,
getLiteralDefaultValue,
} from './defaultValues';

// Validate GraphQL schema.
export { validateSchema, assertValidSchema } from './validate';
16 changes: 10 additions & 6 deletions src/type/introspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { invariant } from '../jsutils/invariant';

import { print } from '../language/printer';
import { DirectiveLocation } from '../language/directiveLocation';
import { astFromValue } from '../utilities/astFromValue';

import type { GraphQLSchema } from './schema';
import type { GraphQLDirective } from './directives';
Expand Down Expand Up @@ -31,6 +30,7 @@ import {
isNonNullType,
isAbstractType,
} from './definition';
import { getLiteralDefaultValue } from './defaultValues';

export const __Schema: GraphQLObjectType = new GraphQLObjectType({
name: '__Schema',
Expand Down Expand Up @@ -382,11 +382,15 @@ export const __InputValue: GraphQLObjectType = new GraphQLObjectType({
type: GraphQLString,
description:
'A GraphQL-formatted string representing the default value for this input value.',
resolve(inputValue) {
const { type, defaultValue } = inputValue;
const valueAST = astFromValue(defaultValue, type);
return valueAST ? print(valueAST) : null;
},
resolve: (inputValue) =>
inputValue.defaultValue
? print(
getLiteralDefaultValue(
inputValue.defaultValue,
inputValue.type,
),
)
: null,
},
isDeprecated: {
type: new GraphQLNonNull(GraphQLBoolean),
Expand Down
6 changes: 2 additions & 4 deletions src/utilities/TypeInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export class TypeInfo {
}
}
this._argument = argDef;
this._defaultValueStack.push(argDef ? argDef.defaultValue : undefined);
this._defaultValueStack.push(argDef?.defaultValue?.value);
this._inputTypeStack.push(isInputType(argType) ? argType : undefined);
break;
}
Expand All @@ -233,9 +233,7 @@ export class TypeInfo {
inputFieldType = inputField.type;
}
}
this._defaultValueStack.push(
inputField ? inputField.defaultValue : undefined,
);
this._defaultValueStack.push(inputField?.defaultValue?.value);
this._inputTypeStack.push(
isInputType(inputFieldType) ? inputFieldType : undefined,
);
Expand Down
7 changes: 5 additions & 2 deletions src/utilities/astFromValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { invariant } from '../jsutils/invariant';
import { isObjectLike } from '../jsutils/isObjectLike';
import { isIterableObject } from '../jsutils/isIterableObject';

import type { ValueNode } from '../language/ast';
import type { ConstValueNode } from '../language/ast';
import { Kind } from '../language/kinds';

import type { GraphQLInputType } from '../type/definition';
Expand Down Expand Up @@ -37,7 +37,10 @@ import {
* | null | NullValue |
*
*/
export function astFromValue(value: mixed, type: GraphQLInputType): ?ValueNode {
export function astFromValue(
value: mixed,
type: GraphQLInputType,
): ?ConstValueNode {
if (isNonNullType(type)) {
const astValue = astFromValue(value, type.ofType);
if (astValue?.kind === Kind.NULL) {
Expand Down

0 comments on commit 29971d0

Please sign in to comment.