Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/utilities/__tests__/buildASTSchema-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,26 @@ describe('Schema Builder', () => {
expect(cycleSDL(sdl)).to.equal(sdl);
});

it('builds recursive input object field defaults', () => {
const sdl = dedent`
type Query {
depth(person: PersonInput): Int
}

input PersonInput {
parent: PersonInput = {parent: {parent: {parent: null}}}
}
`;

const schema = buildSchema(sdl);
const personInput = assertInputObjectType(schema.getType('PersonInput'));

expect(personInput.getFields().parent.defaultValue).to.deep.equal({
parent: { parent: { parent: null } },
});
expect(printSchema(schema)).to.equal(sdl);
});

it('Simple argument field with default', () => {
const sdl = dedent`
type Query {
Expand Down
32 changes: 32 additions & 0 deletions src/utilities/__tests__/extendSchema-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,38 @@ describe('extendSchema', () => {
`);
});

it('extends inputs with recursive field defaults', () => {
const schema = buildSchema(`
type Query {
someInput(arg: PersonInput): String
}

input PersonInput {
parent: PersonInput
}
`);
const extensionSDL = dedent`
extend input PersonInput {
ancestor: PersonInput = {parent: { parent: { parent: null } } }
}
`;
const extendedSchema = extendSchema(schema, parse(extensionSDL));
const personInput = assertInputObjectType(
extendedSchema.getType('PersonInput'),
);

expect(personInput.getFields().ancestor.defaultValue).to.deep.equal({
parent: { parent: { parent: null } },
});
expect(validateSchema(extendedSchema)).to.deep.equal([]);
expectSchemaChanges(schema, extendedSchema).to.equal(dedent`
input PersonInput {
parent: PersonInput
ancestor: PersonInput = {parent: {parent: {parent: null}}}
}
`);
});

it('extends scalars by adding new directives', () => {
const schema = buildSchema(`
type Query {
Expand Down
69 changes: 56 additions & 13 deletions src/utilities/extendSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,15 +298,27 @@ export function extendSchemaImpl(
const config = type.toConfig();
const extensions = typeExtensionsMap[config.name] ?? [];

let fields: GraphQLInputFieldConfigMap | undefined;

return new GraphQLInputObjectType({
...config,
fields: () => ({
...mapValue(config.fields, (field) => ({
...field,
type: replaceType(field.type),
})),
...buildInputFieldMap(extensions),
}),
fields: () => {
if (fields === undefined) {
fields = Object.create(null) as GraphQLInputFieldConfigMap;
Object.assign(
fields,
mapValue(config.fields, (field) => ({
...field,
type: replaceType(field.type),
})),
);
extendInputFieldMap(fields, extensions);
// Default values must be applied _after_ all fields are known to
// prevent issues with recursion.
applyInputFieldDefaultValues(fields, extensions);
}
return fields;
},
extensionASTNodes: config.extensionASTNodes.concat(extensions),
});
}
Expand Down Expand Up @@ -545,12 +557,12 @@ export function extendSchemaImpl(
return argConfigMap;
}

function buildInputFieldMap(
function extendInputFieldMap(
inputFieldMap: GraphQLInputFieldConfigMap,
nodes: ReadonlyArray<
InputObjectTypeDefinitionNode | InputObjectTypeExtensionNode
>,
): GraphQLInputFieldConfigMap {
const inputFieldMap = Object.create(null);
): void {
for (const node of nodes) {
// FIXME: https://github.com/graphql/graphql-js/issues/2203
const fieldsNodes = /* c8 ignore next */ node.fields ?? [];
Expand All @@ -564,13 +576,34 @@ export function extendSchemaImpl(
inputFieldMap[field.name.value] = {
type,
description: field.description?.value,
defaultValue: valueFromAST(field.defaultValue, type),
// `defaultValue` will be populated later by applyInputFieldDefaultValues
defaultValue: undefined,
deprecationReason: getDeprecationReason(field),
astNode: field,
};
}
}
return inputFieldMap;
}

function applyInputFieldDefaultValues(
inputFieldMap: GraphQLInputFieldConfigMap,
nodes: ReadonlyArray<
InputObjectTypeDefinitionNode | InputObjectTypeExtensionNode
>,
): void {
for (const node of nodes) {
// FIXME: https://github.com/graphql/graphql-js/issues/2203
const fieldsNodes = /* c8 ignore next */ node.fields ?? [];

for (const field of fieldsNodes) {
const type = inputFieldMap[field.name.value].type;

inputFieldMap[field.name.value].defaultValue = valueFromAST(
field.defaultValue,
type,
);
}
}
}

function buildEnumValueMap(
Expand Down Expand Up @@ -686,10 +719,20 @@ export function extendSchemaImpl(
case Kind.INPUT_OBJECT_TYPE_DEFINITION: {
const allNodes = [astNode, ...extensionASTNodes];

let fields: GraphQLInputFieldConfigMap | undefined;
return new GraphQLInputObjectType({
name,
description: astNode.description?.value,
fields: () => buildInputFieldMap(allNodes),
fields: () => {
if (fields === undefined) {
fields = Object.create(null) as GraphQLInputFieldConfigMap;
extendInputFieldMap(fields, allNodes);
// Default values must be applied _after_ all fields are known to
// prevent issues with recursion.
applyInputFieldDefaultValues(fields, allNodes);
}
return fields;
},
astNode,
extensionASTNodes,
isOneOf: isOneOf(astNode),
Expand Down
Loading