Skip to content

Commit

Permalink
add support for __directive meta-field
Browse files Browse the repository at this point in the history
analogue to __type
  • Loading branch information
yaacovCR committed Sep 25, 2024
1 parent bd2fe71 commit d32f2e1
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 4 deletions.
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(`

Check failure on line 367 in src/__tests__/starWarsIntrospection-test.ts

View workflow job for this annotation

GitHub Actions / ci / Lint source files

String literals should not contain trailing spaces. If needed for tests please disable locally using eslint comment
{
__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

0 comments on commit d32f2e1

Please sign in to comment.