Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for __directive meta-field #4203

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
42 changes: 42 additions & 0 deletions src/__tests__/starWarsIntrospection-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,5 +362,47 @@ describe('Star Wars Introspection Tests', () => {
},
});
});

it('Allows querying the schema for directives', () => {
const data = queryStarWars(`
{
__directive(name: "skip") {
name
args {
name
type {
kind
name
ofType {
kind
name
}
}
defaultValue
}
}
}
`);

expect(data).to.deep.equal({
__directive: {
name: 'skip',
args: [
{
name: 'if',
type: {
kind: 'NON_NULL',
name: null,
ofType: {
kind: 'SCALAR',
name: 'Boolean',
},
},
defaultValue: null,
},
],
},
});
});
});
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export {
// Meta-field definitions.
SchemaMetaFieldDef,
TypeMetaFieldDef,
DirectiveMetaFieldDef,
TypeNameMetaFieldDef,
// Predicates
isSchema,
Expand Down
26 changes: 26 additions & 0 deletions src/type/__tests__/introspection-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,32 @@ describe('Introspection', () => {
});
});

it('fails as expected on the __directive root field without an arg', () => {
const schema = buildSchema(`
type Query {
someField: String
}
`);

const source = `
{
__directive {
name
}
}
`;

expectJSON(graphqlSync({ schema, source })).toDeepEqual({
errors: [
{
message:
'Argument "<meta>.__directive(name:)" of type "String!" is required, but it was not provided.',
locations: [{ line: 3, column: 9 }],
},
],
});
});

it('exposes descriptions', () => {
const schema = buildSchema(`
"""Enum description"""
Expand Down
2 changes: 2 additions & 0 deletions src/type/__tests__/schema-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '../definition.js';
import { GraphQLDirective } from '../directives.js';
import {
DirectiveMetaFieldDef,
SchemaMetaFieldDef,
TypeMetaFieldDef,
TypeNameMetaFieldDef,
Expand Down Expand Up @@ -413,6 +414,7 @@ describe('Type System: Schema', () => {
expectField(catOrDog, '__typename').to.equal(TypeNameMetaFieldDef);

expectField(queryType, '__type').to.equal(TypeMetaFieldDef);
expectField(queryType, '__directive').to.equal(DirectiveMetaFieldDef);
expectField(queryType, '__schema').to.equal(SchemaMetaFieldDef);
});

Expand Down
1 change: 1 addition & 0 deletions src/type/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export {
// Meta-field definitions.
SchemaMetaFieldDef,
TypeMetaFieldDef,
DirectiveMetaFieldDef,
TypeNameMetaFieldDef,
} from './introspection.js';

Expand Down
22 changes: 22 additions & 0 deletions src/type/introspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,28 @@ export const TypeMetaFieldDef: GraphQLField<unknown, unknown> = {
astNode: undefined,
};

export const DirectiveMetaFieldDef: GraphQLField<unknown, unknown> = {
name: '__directive',
type: __Directive,
description: 'Request information for a single directive.',
args: [
{
name: 'name',
description: undefined,
type: new GraphQLNonNull(GraphQLString),
defaultValue: undefined,
deprecationReason: undefined,
extensions: Object.create(null),
astNode: undefined,
},
],
resolve: (_source, { name }, _context, { schema }) =>
schema.getDirective(name),
deprecationReason: undefined,
extensions: Object.create(null),
astNode: undefined,
};

export const TypeNameMetaFieldDef: GraphQLField<unknown, unknown> = {
name: '__typename',
type: new GraphQLNonNull(GraphQLString),
Expand Down
12 changes: 9 additions & 3 deletions src/type/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type { GraphQLDirective } from './directives.js';
import { isDirective, specifiedDirectives } from './directives.js';
import {
__Schema,
DirectiveMetaFieldDef,
SchemaMetaFieldDef,
TypeMetaFieldDef,
TypeNameMetaFieldDef,
Expand Down Expand Up @@ -340,13 +341,14 @@ export class GraphQLSchema {
/**
* This method looks up the field on the given type definition.
* It has special casing for the three introspection fields, `__schema`,
* `__type` and `__typename`.
* `__type`, `__directive` and `__typename`.
*
* `__typename` is special because it can always be queried as a field, even
* in situations where no other fields are allowed, like on a Union.
*
* `__schema` and `__type` could get automatically added to the query type,
* but that would require mutating type definitions, which would cause issues.
* `__schema`, `__type`, and `__directive` could get automatically added to the
* query type, but that would require mutating type definitions, which would
* cause issues.
*/
getField(
parentType: GraphQLCompositeType,
Expand All @@ -361,6 +363,10 @@ export class GraphQLSchema {
return this.getQueryType() === parentType
? TypeMetaFieldDef
: undefined;
case DirectiveMetaFieldDef.name:
return this.getQueryType() === parentType
? DirectiveMetaFieldDef
: undefined;
case TypeNameMetaFieldDef.name:
return TypeNameMetaFieldDef;
}
Expand Down
5 changes: 5 additions & 0 deletions src/utilities/__tests__/TypeInfo-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ describe('visitWithTypeInfo', () => {
{
__typename
__type(name: "Cat") { __typename }
__directive(name: "skip") { __typename }
__schema {
__typename # in object type
}
Expand All @@ -174,6 +175,7 @@ describe('visitWithTypeInfo', () => {
}
pet {
__type # unknown
__directive # unknown
__schema # unknown
}
}
Expand All @@ -195,6 +197,8 @@ describe('visitWithTypeInfo', () => {
['QueryRoot', '__typename'],
['QueryRoot', '__type'],
['__Type', '__typename'],
['QueryRoot', '__directive'],
['__Directive', '__typename'],
['QueryRoot', '__schema'],
['__Schema', '__typename'],
['QueryRoot', 'humanOrAlien'],
Expand All @@ -206,6 +210,7 @@ describe('visitWithTypeInfo', () => {
['QueryRoot', 'pet'],
['Pet', undefined],
['Pet', undefined],
['Pet', undefined],
]);
});

Expand Down
6 changes: 5 additions & 1 deletion src/validation/rules/MaxIntrospectionDepthRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ export function MaxIntrospectionDepthRule(

return {
Field(node) {
if (node.name.value === '__schema' || node.name.value === '__type') {
if (
node.name.value === '__schema' ||
node.name.value === '__type' ||
node.name.value === '__directive'
) {
if (checkDepth(node)) {
context.reportError(
new GraphQLError('Maximum introspection depth exceeded', {
Expand Down
Loading