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 15, 2021
1 parent 5e2b6d5 commit 5686d3e
Show file tree
Hide file tree
Showing 25 changed files with 275 additions and 81 deletions.
15 changes: 11 additions & 4 deletions src/execution/values.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { typeFromAST } from '../utilities/typeFromAST';
import {
coerceInputValue,
coerceInputLiteral,
coerceDefaultValue,
} from '../utilities/coerceInputValue';

type CoercedVariableValues =
Expand Down Expand Up @@ -178,8 +179,11 @@ export function getArgumentValues(
const argumentNode = argNodeMap[name];

if (!argumentNode) {
if (argDef.defaultValue !== undefined) {
coercedValues[name] = argDef.defaultValue;
if (argDef.defaultValue) {
coercedValues[name] = coerceDefaultValue(
argDef.defaultValue,
argDef.type,
);
} else if (isNonNullType(argType)) {
throw new GraphQLError(
`Argument "${name}" of required type "${inspect(argType)}" ` +
Expand All @@ -199,8 +203,11 @@ export function getArgumentValues(
variableValues == null ||
!hasOwnProperty(variableValues, variableName)
) {
if (argDef.defaultValue !== undefined) {
coercedValues[name] = argDef.defaultValue;
if (argDef.defaultValue) {
coercedValues[name] = coerceDefaultValue(
argDef.defaultValue,
argDef.type,
);
} else if (isNonNullType(argType)) {
throw new GraphQLError(
`Argument "${name}" of required type "${inspect(argType)}" ` +
Expand Down
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export {
GraphQLArgumentConfig,
GraphQLArgumentExtensions,
GraphQLInputValue,
GraphQLDefaultValueUsage,
GraphQLInputValueConfig,
GraphQLEnumTypeConfig,
GraphQLEnumTypeExtensions,
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export type {
GraphQLArgument,
GraphQLArgumentConfig,
GraphQLInputValue,
GraphQLDefaultValueUsage,
GraphQLInputValueConfig,
GraphQLEnumTypeConfig,
GraphQLEnumValue,
Expand Down
61 changes: 61 additions & 0 deletions src/type/__tests__/definition-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,67 @@ describe('Type System: Input Objects', () => {
);
});
});

describe('Input Object fields may have default values', () => {
it('accepts an Input Object type with a default value', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields: {
f: { type: ScalarType, defaultValue: 3 },
},
});
expect(inputObjType.getFields()).to.deep.equal({
f: {
name: 'f',
description: undefined,
type: ScalarType,
defaultValue: { value: 3 },
deprecationReason: undefined,
extensions: undefined,
astNode: undefined,
},
});
});

it('accepts an Input Object type with a default value literal', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields: {
f: {
type: ScalarType,
defaultValueLiteral: { kind: 'IntValue', value: '3' },
},
},
});
expect(inputObjType.getFields()).to.deep.equal({
f: {
name: 'f',
description: undefined,
type: ScalarType,
defaultValue: { literal: { kind: 'IntValue', value: '3' } },
deprecationReason: undefined,
extensions: undefined,
astNode: undefined,
},
});
});

it('rejects an Input Object type with potentially conflicting default values', () => {
const inputObjType = new GraphQLInputObjectType({
name: 'SomeInputObject',
fields: {
f: {
type: ScalarType,
defaultValue: 3,
defaultValueLiteral: { kind: 'IntValue', value: '3' },
},
},
});
expect(() => inputObjType.getFields()).to.throw(
'f has both a defaultValue and a defaultValueLiteral property, but only one must be provided.',
);
});
});
});

describe('Type System: List', () => {
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
7 changes: 6 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,16 @@ 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 type GraphQLDefaultValueUsage =
| { value: unknown; literal?: never }
| { literal: ConstValueNode; value?: never };

export interface GraphQLInputValueConfig<Extensions> {
description?: Maybe<string>;
type: GraphQLInputType;
Expand Down
27 changes: 24 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,21 @@ export function defineInputValue(
!('resolve' in config),
`${name} has a resolve property, but inputs cannot define resolvers.`,
);
let defaultValue;
if (config.defaultValue !== undefined || config.defaultValueLiteral) {
devAssert(
config.defaultValue === undefined || !config.defaultValueLiteral,
`${name} has both a defaultValue and a defaultValueLiteral property, but only one must be provided.`,
);
defaultValue = config.defaultValueLiteral
? { literal: config.defaultValueLiteral }
: { value: config.defaultValue };
}
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 +1002,12 @@ export function inputValueToConfig(
return {
description: inputValue.description,
type: inputValue.type,
defaultValue: inputValue.defaultValue,
defaultValue: inputValue.defaultValue?.literal
? undefined
: inputValue.defaultValue?.value,
defaultValueLiteral: inputValue.defaultValue?.literal
? inputValue.defaultValue.literal
: undefined,
deprecationReason: inputValue.deprecationReason,
extensions: inputValue.extensions,
astNode: inputValue.astNode,
Expand All @@ -1002,16 +1018,21 @@ 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
1 change: 1 addition & 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
1 change: 1 addition & 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 Down
10 changes: 8 additions & 2 deletions src/type/introspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,14 @@ export const __InputValue: GraphQLObjectType = new GraphQLObjectType({
'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;
if (!defaultValue) {
return null;
}
const literal = defaultValue.literal
? defaultValue.literal
: astFromValue(defaultValue.value, type);
invariant(literal, 'Invalid default value');
return print(literal);
},
},
isDeprecated: {
Expand Down
3 changes: 2 additions & 1 deletion src/utilities/TypeInfo.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
GraphQLField,
GraphQLArgument,
GraphQLEnumValue,
GraphQLDefaultValueUsage,
} from '../type/definition';

/**
Expand All @@ -35,7 +36,7 @@ export class TypeInfo {
getInputType(): Maybe<GraphQLInputType>;
getParentInputType(): Maybe<GraphQLInputType>;
getFieldDef(): Maybe<GraphQLField<unknown, unknown>>;
getDefaultValue(): Maybe<unknown>;
getDefaultValue(): Maybe<GraphQLDefaultValueUsage>;
getDirective(): Maybe<GraphQLDirective>;
getArgument(): Maybe<GraphQLArgument>;
getEnumValue(): Maybe<GraphQLEnumValue>;
Expand Down
11 changes: 5 additions & 6 deletions src/utilities/TypeInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
GraphQLArgument,
GraphQLInputField,
GraphQLEnumValue,
GraphQLDefaultValueUsage,
} from '../type/definition';
import {
isObjectType,
Expand Down Expand Up @@ -47,7 +48,7 @@ export class TypeInfo {
_parentTypeStack: Array<?GraphQLCompositeType>;
_inputTypeStack: Array<?GraphQLInputType>;
_fieldDefStack: Array<?GraphQLField<mixed, mixed>>;
_defaultValueStack: Array<?mixed>;
_defaultValueStack: Array<?GraphQLDefaultValueUsage>;
_directive: ?GraphQLDirective;
_argument: ?GraphQLArgument;
_enumValue: ?GraphQLEnumValue;
Expand Down Expand Up @@ -115,7 +116,7 @@ export class TypeInfo {
}
}

getDefaultValue(): ?mixed {
getDefaultValue(): ?GraphQLDefaultValueUsage {
if (this._defaultValueStack.length > 0) {
return this._defaultValueStack[this._defaultValueStack.length - 1];
}
Expand Down Expand Up @@ -209,7 +210,7 @@ export class TypeInfo {
}
}
this._argument = argDef;
this._defaultValueStack.push(argDef ? argDef.defaultValue : undefined);
this._defaultValueStack.push(argDef?.defaultValue);
this._inputTypeStack.push(isInputType(argType) ? argType : undefined);
break;
}
Expand All @@ -233,9 +234,7 @@ export class TypeInfo {
inputFieldType = inputField.type;
}
}
this._defaultValueStack.push(
inputField ? inputField.defaultValue : undefined,
);
this._defaultValueStack.push(inputField?.defaultValue);
this._inputTypeStack.push(
isInputType(inputFieldType) ? inputFieldType : undefined,
);
Expand Down
2 changes: 2 additions & 0 deletions src/utilities/__tests__/astFromValue-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ describe('astFromValue', () => {
kind: 'BooleanValue',
value: false,
});

expect(astFromValue(null, NonNullBoolean)).to.equal(null);
});

it('converts Int values to Int ASTs', () => {
Expand Down
Loading

0 comments on commit 5686d3e

Please sign in to comment.