Skip to content

Commit cc4c363

Browse files
introspection: Add support for repeatable directives (#2416)
Code is taken from #1541
1 parent 69c4a09 commit cc4c363

9 files changed

+92
-8
lines changed

src/type/__tests__/introspection-test.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ describe('Introspection', () => {
2727
},
2828
}),
2929
});
30-
const source = getIntrospectionQuery({ descriptions: false });
30+
const source = getIntrospectionQuery({
31+
descriptions: false,
32+
directiveIsRepeatable: true,
33+
});
3134

3235
const result = graphqlSync({ schema, source });
3336
expect(result).to.deep.equal({
@@ -648,6 +651,21 @@ describe('Introspection', () => {
648651
isDeprecated: false,
649652
deprecationReason: null,
650653
},
654+
{
655+
name: 'isRepeatable',
656+
args: [],
657+
type: {
658+
kind: 'NON_NULL',
659+
name: null,
660+
ofType: {
661+
kind: 'SCALAR',
662+
name: 'Boolean',
663+
ofType: null,
664+
},
665+
},
666+
isDeprecated: false,
667+
deprecationReason: null,
668+
},
651669
{
652670
name: 'locations',
653671
args: [],
@@ -809,6 +827,7 @@ describe('Introspection', () => {
809827
directives: [
810828
{
811829
name: 'include',
830+
isRepeatable: false,
812831
locations: ['FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT'],
813832
args: [
814833
{
@@ -828,6 +847,7 @@ describe('Introspection', () => {
828847
},
829848
{
830849
name: 'skip',
850+
isRepeatable: false,
831851
locations: ['FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT'],
832852
args: [
833853
{
@@ -847,6 +867,7 @@ describe('Introspection', () => {
847867
},
848868
{
849869
name: 'deprecated',
870+
isRepeatable: false,
850871
locations: ['FIELD_DEFINITION', 'ENUM_VALUE'],
851872
args: [
852873
{
@@ -1394,7 +1415,7 @@ describe('Introspection', () => {
13941415
});
13951416

13961417
const schema = new GraphQLSchema({ query: QueryRoot });
1397-
const source = getIntrospectionQuery();
1418+
const source = getIntrospectionQuery({ directiveIsRepeatable: true });
13981419

13991420
/* istanbul ignore next */
14001421
function fieldResolver(_1, _2, _3, info) {

src/type/introspection.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export const __Directive = new GraphQLObjectType({
8989
type: GraphQLString,
9090
resolve: obj => obj.description,
9191
},
92+
isRepeatable: {
93+
type: GraphQLNonNull(GraphQLBoolean),
94+
resolve: obj => obj.isRepeatable,
95+
},
9296
locations: {
9397
type: GraphQLNonNull(GraphQLList(GraphQLNonNull(__DirectiveLocation))),
9498
resolve: obj => obj.locations,

src/utilities/__tests__/buildClientSchema-test.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ import { introspectionFromSchema } from '../introspectionFromSchema';
3333
* returns that schema printed as SDL.
3434
*/
3535
function cycleIntrospection(sdlString: string): string {
36+
const options = { directiveIsRepeatable: true };
37+
3638
const serverSchema = buildSchema(sdlString);
37-
const initialIntrospection = introspectionFromSchema(serverSchema);
39+
const initialIntrospection = introspectionFromSchema(serverSchema, options);
3840
const clientSchema = buildClientSchema(initialIntrospection);
39-
const secondIntrospection = introspectionFromSchema(clientSchema);
41+
const secondIntrospection = introspectionFromSchema(clientSchema, options);
4042

4143
/**
4244
* If the client then runs the introspection query against the client-side
@@ -457,7 +459,7 @@ describe('Type System: build schema from introspection', () => {
457459
it('builds a schema with custom directives', () => {
458460
const sdl = dedent`
459461
"""This is a custom directive"""
460-
directive @customDirective on FIELD
462+
directive @customDirective repeatable on FIELD
461463
462464
type Query {
463465
string: String
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @flow strict
2+
3+
import { expect } from 'chai';
4+
import { describe, it } from 'mocha';
5+
6+
import { getIntrospectionQuery } from '../getIntrospectionQuery';
7+
8+
describe('getIntrospectionQuery', () => {
9+
it('skip all "description" fields', () => {
10+
expect(getIntrospectionQuery()).to.match(/\bdescription\b/);
11+
12+
expect(getIntrospectionQuery({ descriptions: true })).to.match(
13+
/\bdescription\b/,
14+
);
15+
16+
expect(getIntrospectionQuery({ descriptions: false })).to.not.match(
17+
/\bdescription\b/,
18+
);
19+
});
20+
21+
it('include "isRepeatable" field', () => {
22+
expect(getIntrospectionQuery()).to.not.match(/\bisRepeatable\b/);
23+
24+
expect(getIntrospectionQuery({ directiveIsRepeatable: true })).to.match(
25+
/\bisRepeatable\b/,
26+
);
27+
28+
expect(
29+
getIntrospectionQuery({ directiveIsRepeatable: false }),
30+
).to.not.match(/\bisRepeatable\b/);
31+
});
32+
});

src/utilities/__tests__/schemaPrinter-test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,7 @@ describe('Type System Printer', () => {
740740
type __Directive {
741741
name: String!
742742
description: String
743+
isRepeatable: Boolean!
743744
locations: [__DirectiveLocation!]!
744745
args: [__InputValue!]!
745746
}
@@ -927,6 +928,7 @@ describe('Type System Printer', () => {
927928
type __Directive {
928929
name: String!
929930
description: String
931+
isRepeatable: Boolean!
930932
locations: [__DirectiveLocation!]!
931933
args: [__InputValue!]!
932934
}

src/utilities/buildClientSchema.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ export function buildClientSchema(
385385
return new GraphQLDirective({
386386
name: directiveIntrospection.name,
387387
description: directiveIntrospection.description,
388+
isRepeatable: directiveIntrospection.isRepeatable,
388389
locations: directiveIntrospection.locations.slice(),
389390
args: buildInputValueDefMap(directiveIntrospection.args),
390391
});

src/utilities/getIntrospectionQuery.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export interface IntrospectionOptions {
55
// Whether to include descriptions in the introspection result.
66
// Default: true
77
descriptions: boolean;
8+
9+
// Whether to include `isRepeatable` flag on directives.
10+
// Default: false
11+
directiveIsRepeatable?: boolean;
812
}
913

1014
export function getIntrospectionQuery(options?: IntrospectionOptions): string;
@@ -167,6 +171,7 @@ export interface IntrospectionEnumValue {
167171
export interface IntrospectionDirective {
168172
readonly name: string;
169173
readonly description?: Maybe<string>;
174+
readonly isRepeatable?: boolean;
170175
readonly locations: ReadonlyArray<DirectiveLocationEnum>;
171176
readonly args: ReadonlyArray<IntrospectionInputValue>;
172177
}

src/utilities/getIntrospectionQuery.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,25 @@ import { type DirectiveLocationEnum } from '../language/directiveLocation';
55
export type IntrospectionOptions = {|
66
// Whether to include descriptions in the introspection result.
77
// Default: true
8-
descriptions: boolean,
8+
descriptions?: boolean,
9+
10+
// Whether to include `isRepeatable` flag on directives.
11+
// Default: false
12+
directiveIsRepeatable?: boolean,
913
|};
1014

1115
export function getIntrospectionQuery(options?: IntrospectionOptions): string {
12-
const descriptions = options?.descriptions !== false ? 'description' : '';
16+
const optionsWithDefault = {
17+
descriptions: true,
18+
directiveIsRepeatable: false,
19+
...options,
20+
};
21+
22+
const descriptions = optionsWithDefault.descriptions ? 'description' : '';
23+
const directiveIsRepeatable = optionsWithDefault.directiveIsRepeatable
24+
? 'isRepeatable'
25+
: '';
26+
1327
return `
1428
query IntrospectionQuery {
1529
__schema {
@@ -22,6 +36,7 @@ export function getIntrospectionQuery(options?: IntrospectionOptions): string {
2236
directives {
2337
name
2438
${descriptions}
39+
${directiveIsRepeatable}
2540
locations
2641
args {
2742
...InputValue
@@ -259,6 +274,7 @@ export type IntrospectionEnumValue = {|
259274
export type IntrospectionDirective = {|
260275
+name: string,
261276
+description?: ?string,
277+
+isRepeatable?: boolean,
262278
+locations: $ReadOnlyArray<DirectiveLocationEnum>,
263279
+args: $ReadOnlyArray<IntrospectionInputValue>,
264280
|};

src/utilities/introspectionFromSchema.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export function introspectionFromSchema(
2626
schema: GraphQLSchema,
2727
options?: IntrospectionOptions,
2828
): IntrospectionQuery {
29-
const document = parse(getIntrospectionQuery(options));
29+
const optionsWithDefaults = { directiveIsRepeatable: true, ...options };
30+
const document = parse(getIntrospectionQuery(optionsWithDefaults));
3031
const result = execute({ schema, document });
3132
invariant(!isPromise(result) && !result.errors && result.data);
3233
return (result.data: any);

0 commit comments

Comments
 (0)