Skip to content

introspection: Add support for repeatable directives #2416

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

Merged
merged 1 commit into from
Jan 30, 2020
Merged
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
25 changes: 23 additions & 2 deletions src/type/__tests__/introspection-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ describe('Introspection', () => {
},
}),
});
const source = getIntrospectionQuery({ descriptions: false });
const source = getIntrospectionQuery({
descriptions: false,
directiveIsRepeatable: true,
});

const result = graphqlSync({ schema, source });
expect(result).to.deep.equal({
Expand Down Expand Up @@ -648,6 +651,21 @@ describe('Introspection', () => {
isDeprecated: false,
deprecationReason: null,
},
{
name: 'isRepeatable',
args: [],
type: {
kind: 'NON_NULL',
name: null,
ofType: {
kind: 'SCALAR',
name: 'Boolean',
ofType: null,
},
},
isDeprecated: false,
deprecationReason: null,
},
{
name: 'locations',
args: [],
Expand Down Expand Up @@ -809,6 +827,7 @@ describe('Introspection', () => {
directives: [
{
name: 'include',
isRepeatable: false,
locations: ['FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT'],
args: [
{
Expand All @@ -828,6 +847,7 @@ describe('Introspection', () => {
},
{
name: 'skip',
isRepeatable: false,
locations: ['FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT'],
args: [
{
Expand All @@ -847,6 +867,7 @@ describe('Introspection', () => {
},
{
name: 'deprecated',
isRepeatable: false,
locations: ['FIELD_DEFINITION', 'ENUM_VALUE'],
args: [
{
Expand Down Expand Up @@ -1394,7 +1415,7 @@ describe('Introspection', () => {
});

const schema = new GraphQLSchema({ query: QueryRoot });
const source = getIntrospectionQuery();
const source = getIntrospectionQuery({ directiveIsRepeatable: true });

/* istanbul ignore next */
function fieldResolver(_1, _2, _3, info) {
Expand Down
4 changes: 4 additions & 0 deletions src/type/introspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export const __Directive = new GraphQLObjectType({
type: GraphQLString,
resolve: obj => obj.description,
},
isRepeatable: {
type: GraphQLNonNull(GraphQLBoolean),
resolve: obj => obj.isRepeatable,
},
locations: {
type: GraphQLNonNull(GraphQLList(GraphQLNonNull(__DirectiveLocation))),
resolve: obj => obj.locations,
Expand Down
8 changes: 5 additions & 3 deletions src/utilities/__tests__/buildClientSchema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ import { introspectionFromSchema } from '../introspectionFromSchema';
* returns that schema printed as SDL.
*/
function cycleIntrospection(sdlString: string): string {
const options = { directiveIsRepeatable: true };

const serverSchema = buildSchema(sdlString);
const initialIntrospection = introspectionFromSchema(serverSchema);
const initialIntrospection = introspectionFromSchema(serverSchema, options);
const clientSchema = buildClientSchema(initialIntrospection);
const secondIntrospection = introspectionFromSchema(clientSchema);
const secondIntrospection = introspectionFromSchema(clientSchema, options);

/**
* If the client then runs the introspection query against the client-side
Expand Down Expand Up @@ -457,7 +459,7 @@ describe('Type System: build schema from introspection', () => {
it('builds a schema with custom directives', () => {
const sdl = dedent`
"""This is a custom directive"""
directive @customDirective on FIELD
directive @customDirective repeatable on FIELD

type Query {
string: String
Expand Down
32 changes: 32 additions & 0 deletions src/utilities/__tests__/getIntrospectionQuery-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// @flow strict

import { expect } from 'chai';
import { describe, it } from 'mocha';

import { getIntrospectionQuery } from '../getIntrospectionQuery';

describe('getIntrospectionQuery', () => {
it('skip all "description" fields', () => {
expect(getIntrospectionQuery()).to.match(/\bdescription\b/);

expect(getIntrospectionQuery({ descriptions: true })).to.match(
/\bdescription\b/,
);

expect(getIntrospectionQuery({ descriptions: false })).to.not.match(
/\bdescription\b/,
);
});

it('include "isRepeatable" field', () => {
expect(getIntrospectionQuery()).to.not.match(/\bisRepeatable\b/);

expect(getIntrospectionQuery({ directiveIsRepeatable: true })).to.match(
/\bisRepeatable\b/,
);

expect(
getIntrospectionQuery({ directiveIsRepeatable: false }),
).to.not.match(/\bisRepeatable\b/);
});
});
2 changes: 2 additions & 0 deletions src/utilities/__tests__/schemaPrinter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@ describe('Type System Printer', () => {
type __Directive {
name: String!
description: String
isRepeatable: Boolean!
locations: [__DirectiveLocation!]!
args: [__InputValue!]!
}
Expand Down Expand Up @@ -927,6 +928,7 @@ describe('Type System Printer', () => {
type __Directive {
name: String!
description: String
isRepeatable: Boolean!
locations: [__DirectiveLocation!]!
args: [__InputValue!]!
}
Expand Down
1 change: 1 addition & 0 deletions src/utilities/buildClientSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ export function buildClientSchema(
return new GraphQLDirective({
name: directiveIntrospection.name,
description: directiveIntrospection.description,
isRepeatable: directiveIntrospection.isRepeatable,
locations: directiveIntrospection.locations.slice(),
args: buildInputValueDefMap(directiveIntrospection.args),
});
Expand Down
5 changes: 5 additions & 0 deletions src/utilities/getIntrospectionQuery.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export interface IntrospectionOptions {
// Whether to include descriptions in the introspection result.
// Default: true
descriptions: boolean;

// Whether to include `isRepeatable` flag on directives.
// Default: false
directiveIsRepeatable?: boolean;
}

export function getIntrospectionQuery(options?: IntrospectionOptions): string;
Expand Down Expand Up @@ -167,6 +171,7 @@ export interface IntrospectionEnumValue {
export interface IntrospectionDirective {
readonly name: string;
readonly description?: Maybe<string>;
readonly isRepeatable?: boolean;
readonly locations: ReadonlyArray<DirectiveLocationEnum>;
readonly args: ReadonlyArray<IntrospectionInputValue>;
}
20 changes: 18 additions & 2 deletions src/utilities/getIntrospectionQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,25 @@ import { type DirectiveLocationEnum } from '../language/directiveLocation';
export type IntrospectionOptions = {|
// Whether to include descriptions in the introspection result.
// Default: true
descriptions: boolean,
descriptions?: boolean,

// Whether to include `isRepeatable` flag on directives.
// Default: false
directiveIsRepeatable?: boolean,
|};

export function getIntrospectionQuery(options?: IntrospectionOptions): string {
const descriptions = options?.descriptions !== false ? 'description' : '';
const optionsWithDefault = {
descriptions: true,
directiveIsRepeatable: false,
...options,
};

const descriptions = optionsWithDefault.descriptions ? 'description' : '';
const directiveIsRepeatable = optionsWithDefault.directiveIsRepeatable
? 'isRepeatable'
: '';

return `
query IntrospectionQuery {
__schema {
Expand All @@ -22,6 +36,7 @@ export function getIntrospectionQuery(options?: IntrospectionOptions): string {
directives {
name
${descriptions}
${directiveIsRepeatable}
locations
args {
...InputValue
Expand Down Expand Up @@ -259,6 +274,7 @@ export type IntrospectionEnumValue = {|
export type IntrospectionDirective = {|
+name: string,
+description?: ?string,
+isRepeatable?: boolean,
+locations: $ReadOnlyArray<DirectiveLocationEnum>,
+args: $ReadOnlyArray<IntrospectionInputValue>,
|};
3 changes: 2 additions & 1 deletion src/utilities/introspectionFromSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export function introspectionFromSchema(
schema: GraphQLSchema,
options?: IntrospectionOptions,
): IntrospectionQuery {
const document = parse(getIntrospectionQuery(options));
const optionsWithDefaults = { directiveIsRepeatable: true, ...options };
const document = parse(getIntrospectionQuery(optionsWithDefaults));
const result = execute({ schema, document });
invariant(!isPromise(result) && !result.errors && result.data);
return (result.data: any);
Expand Down