diff --git a/src/__tests__/starWarsIntrospection-test.ts b/src/__tests__/starWarsIntrospection-test.ts
index 224c506c19..ce08f208ee 100644
--- a/src/__tests__/starWarsIntrospection-test.ts
+++ b/src/__tests__/starWarsIntrospection-test.ts
@@ -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,
+ },
+ ],
+ },
+ });
+ });
});
});
diff --git a/src/index.ts b/src/index.ts
index 27ec4301d5..f3be80c809 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -84,6 +84,7 @@ export {
// Meta-field definitions.
SchemaMetaFieldDef,
TypeMetaFieldDef,
+ DirectiveMetaFieldDef,
TypeNameMetaFieldDef,
// Predicates
isSchema,
diff --git a/src/type/__tests__/introspection-test.ts b/src/type/__tests__/introspection-test.ts
index c914570713..c1d75208ab 100644
--- a/src/type/__tests__/introspection-test.ts
+++ b/src/type/__tests__/introspection-test.ts
@@ -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 ".__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"""
diff --git a/src/type/__tests__/schema-test.ts b/src/type/__tests__/schema-test.ts
index 5bd86e030a..6dc4409d07 100644
--- a/src/type/__tests__/schema-test.ts
+++ b/src/type/__tests__/schema-test.ts
@@ -18,6 +18,7 @@ import {
} from '../definition.js';
import { GraphQLDirective } from '../directives.js';
import {
+ DirectiveMetaFieldDef,
SchemaMetaFieldDef,
TypeMetaFieldDef,
TypeNameMetaFieldDef,
@@ -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);
});
diff --git a/src/type/index.ts b/src/type/index.ts
index dfbca75ad0..e9042d19ab 100644
--- a/src/type/index.ts
+++ b/src/type/index.ts
@@ -182,6 +182,7 @@ export {
// Meta-field definitions.
SchemaMetaFieldDef,
TypeMetaFieldDef,
+ DirectiveMetaFieldDef,
TypeNameMetaFieldDef,
} from './introspection.js';
diff --git a/src/type/introspection.ts b/src/type/introspection.ts
index b72b162cbb..998717df6d 100644
--- a/src/type/introspection.ts
+++ b/src/type/introspection.ts
@@ -546,6 +546,28 @@ export const TypeMetaFieldDef: GraphQLField = {
astNode: undefined,
};
+export const DirectiveMetaFieldDef: GraphQLField = {
+ 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 = {
name: '__typename',
type: new GraphQLNonNull(GraphQLString),
diff --git a/src/type/schema.ts b/src/type/schema.ts
index 694454fae5..abc95ae19b 100644
--- a/src/type/schema.ts
+++ b/src/type/schema.ts
@@ -32,6 +32,7 @@ import type { GraphQLDirective } from './directives.js';
import { isDirective, specifiedDirectives } from './directives.js';
import {
__Schema,
+ DirectiveMetaFieldDef,
SchemaMetaFieldDef,
TypeMetaFieldDef,
TypeNameMetaFieldDef,
@@ -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,
@@ -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;
}
diff --git a/src/utilities/__tests__/TypeInfo-test.ts b/src/utilities/__tests__/TypeInfo-test.ts
index cc129287c3..e2d9c78bd0 100644
--- a/src/utilities/__tests__/TypeInfo-test.ts
+++ b/src/utilities/__tests__/TypeInfo-test.ts
@@ -160,6 +160,7 @@ describe('visitWithTypeInfo', () => {
{
__typename
__type(name: "Cat") { __typename }
+ __directive(name: "skip") { __typename }
__schema {
__typename # in object type
}
@@ -174,6 +175,7 @@ describe('visitWithTypeInfo', () => {
}
pet {
__type # unknown
+ __directive # unknown
__schema # unknown
}
}
@@ -195,6 +197,8 @@ describe('visitWithTypeInfo', () => {
['QueryRoot', '__typename'],
['QueryRoot', '__type'],
['__Type', '__typename'],
+ ['QueryRoot', '__directive'],
+ ['__Directive', '__typename'],
['QueryRoot', '__schema'],
['__Schema', '__typename'],
['QueryRoot', 'humanOrAlien'],
@@ -206,6 +210,7 @@ describe('visitWithTypeInfo', () => {
['QueryRoot', 'pet'],
['Pet', undefined],
['Pet', undefined],
+ ['Pet', undefined],
]);
});
diff --git a/src/validation/rules/MaxIntrospectionDepthRule.ts b/src/validation/rules/MaxIntrospectionDepthRule.ts
index 5cde22f130..090b7a99eb 100644
--- a/src/validation/rules/MaxIntrospectionDepthRule.ts
+++ b/src/validation/rules/MaxIntrospectionDepthRule.ts
@@ -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', {