Skip to content

Commit

Permalink
fix(pruneSchema): respect directive definitions in extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Jul 13, 2024
1 parent e07bb6d commit a276ba8
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/pretty-foxes-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-tools/utils": patch
---

Respect directive extensions on \`pruneSchema\`
51 changes: 30 additions & 21 deletions packages/utils/src/prune.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
ASTNode,
getNamedType,
GraphQLFieldMap,
GraphQLSchema,
Expand All @@ -11,6 +10,7 @@ import {
isSpecifiedScalarType,
isUnionType,
} from 'graphql';
import { DirectableGraphQLObject } from './get-directives.js';
import { getImplementingTypes } from './get-implementing-types.js';
import { MapperKind } from './Interfaces.js';
import { mapSchema } from './mapSchema.js';
Expand Down Expand Up @@ -152,12 +152,7 @@ function visitQueue(
if (isEnumType(type)) {
// Visit enum values directives argument types
queue.push(
...type.getValues().flatMap(value => {
if (value.astNode) {
return getDirectivesArgumentsTypeNames(schema, value.astNode);
}
return [];
}),
...type.getValues().flatMap(value => getDirectivesArgumentsTypeNames(schema, value)),
);
}
// Visit interfaces this type is implementing if they haven't been visited yet
Expand All @@ -180,9 +175,7 @@ function visitQueue(
queue.push(
...field.args.flatMap(arg => {
const typeNames = [getNamedType(arg.type).name];
if (arg.astNode) {
typeNames.push(...getDirectivesArgumentsTypeNames(schema, arg.astNode));
}
typeNames.push(...getDirectivesArgumentsTypeNames(schema, arg));
return typeNames;
}),
);
Expand All @@ -192,9 +185,7 @@ function visitQueue(

queue.push(namedType.name);

if (field.astNode) {
queue.push(...getDirectivesArgumentsTypeNames(schema, field.astNode));
}
queue.push(...getDirectivesArgumentsTypeNames(schema, field));

// Interfaces returned on fields need to be revisited to add their implementations
if (isInterfaceType(namedType) && !(namedType.name in revisit)) {
Expand All @@ -203,9 +194,7 @@ function visitQueue(
}
}

if (type.astNode) {
queue.push(...getDirectivesArgumentsTypeNames(schema, type.astNode));
}
queue.push(...getDirectivesArgumentsTypeNames(schema, type));

visited.add(typeName); // Mark as visited (and therefore it is used and should be kept)
}
Expand All @@ -215,10 +204,30 @@ function visitQueue(

function getDirectivesArgumentsTypeNames(
schema: GraphQLSchema,
astNode: Extract<ASTNode, { readonly directives?: any }>,
directableObj: DirectableGraphQLObject,
) {
return (astNode.directives ?? []).flatMap(
directive =>
schema.getDirective(directive.name.value)?.args.map(arg => getNamedType(arg.type).name) ?? [],
);
const argTypeNames = new Set<string>();
if (directableObj.astNode?.directives) {
for (const directiveNode of directableObj.astNode.directives) {
const directive = schema.getDirective(directiveNode.name.value);
if (directive?.args) {
for (const arg of directive.args) {
const argType = getNamedType(arg.type);
argTypeNames.add(argType.name);
}
}
}
}
if (directableObj.extensions?.['directives']) {
for (const directiveName in directableObj.extensions['directives']) {
const directive = schema.getDirective(directiveName);
if (directive?.args) {
for (const arg of directive.args) {
const argType = getNamedType(arg.type);
argTypeNames.add(argType.name);
}
}
}
}
return [...argTypeNames];
}
54 changes: 53 additions & 1 deletion packages/utils/tests/prune.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { buildSchema, GraphQLNamedType } from 'graphql';
import {
buildSchema,
DirectiveLocation,
GraphQLDirective,
GraphQLEnumType,
GraphQLNamedType,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
} from 'graphql';
import { PruneSchemaFilter } from '../src/index.js';
import { pruneSchema } from '../src/prune.js';

Expand Down Expand Up @@ -487,6 +496,49 @@ describe('pruneSchema', () => {
}
`);

const result = pruneSchema(schema);
expect(result.getType('DirectiveArg')).toBeDefined();
});
test('does not remove type used in argument definition directive argument from extensions', () => {
const enumType = new GraphQLEnumType({
name: 'DirectiveArg',
values: {
VALUE: {
value: 'VALUE',
},
},
});
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
foo: {
type: GraphQLString,
extensions: {
directives: {
bar: [
{
arg: 'VALUE',
},
],
},
},
},
},
}),
directives: [
new GraphQLDirective({
name: 'bar',
locations: [DirectiveLocation.FIELD],
args: {
arg: {
type: enumType,
},
},
}),
],
});

const result = pruneSchema(schema);
expect(result.getType('DirectiveArg')).toBeDefined();
});
Expand Down

0 comments on commit a276ba8

Please sign in to comment.