Skip to content

Commit 062ccc4

Browse files
committed
Add @specified directive
This in an implementation for a spec proposal: * Spec proposal: [[RFC] Custom Scalar Specification URIs](graphql/graphql-spec#649) * Original issue: [[RFC] Custom Scalar Specification URIs](graphql/graphql-spec#635)
1 parent 61a07b1 commit 062ccc4

10 files changed

+123
-10
lines changed

src/type/__tests__/introspection-test.js

+41
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ describe('Introspection', () => {
6161
interfaces: [],
6262
enumValues: null,
6363
possibleTypes: null,
64+
specifiedBy: null,
6465
},
6566
{
6667
kind: 'SCALAR',
@@ -70,6 +71,7 @@ describe('Introspection', () => {
7071
interfaces: null,
7172
enumValues: null,
7273
possibleTypes: null,
74+
specifiedBy: null,
7375
},
7476
{
7577
kind: 'OBJECT',
@@ -163,6 +165,7 @@ describe('Introspection', () => {
163165
interfaces: [],
164166
enumValues: null,
165167
possibleTypes: null,
168+
specifiedBy: null,
166169
},
167170
{
168171
kind: 'OBJECT',
@@ -205,6 +208,17 @@ describe('Introspection', () => {
205208
isDeprecated: false,
206209
deprecationReason: null,
207210
},
211+
{
212+
args: [],
213+
deprecationReason: null,
214+
isDeprecated: false,
215+
name: 'specifiedBy',
216+
type: {
217+
kind: 'SCALAR',
218+
name: 'String',
219+
ofType: null,
220+
},
221+
},
208222
{
209223
name: 'fields',
210224
args: [
@@ -336,6 +350,7 @@ describe('Introspection', () => {
336350
interfaces: [],
337351
enumValues: null,
338352
possibleTypes: null,
353+
specifiedBy: null,
339354
},
340355
{
341356
kind: 'ENUM',
@@ -386,6 +401,7 @@ describe('Introspection', () => {
386401
},
387402
],
388403
possibleTypes: null,
404+
specifiedBy: null,
389405
},
390406
{
391407
kind: 'SCALAR',
@@ -395,6 +411,7 @@ describe('Introspection', () => {
395411
interfaces: null,
396412
enumValues: null,
397413
possibleTypes: null,
414+
specifiedBy: null,
398415
},
399416
{
400417
kind: 'OBJECT',
@@ -495,6 +512,7 @@ describe('Introspection', () => {
495512
interfaces: [],
496513
enumValues: null,
497514
possibleTypes: null,
515+
specifiedBy: null,
498516
},
499517
{
500518
kind: 'OBJECT',
@@ -557,6 +575,7 @@ describe('Introspection', () => {
557575
interfaces: [],
558576
enumValues: null,
559577
possibleTypes: null,
578+
specifiedBy: null,
560579
},
561580
{
562581
kind: 'OBJECT',
@@ -619,6 +638,7 @@ describe('Introspection', () => {
619638
interfaces: [],
620639
enumValues: null,
621640
possibleTypes: null,
641+
specifiedBy: null,
622642
},
623643
{
624644
kind: 'OBJECT',
@@ -701,6 +721,7 @@ describe('Introspection', () => {
701721
interfaces: [],
702722
enumValues: null,
703723
possibleTypes: null,
724+
specifiedBy: null,
704725
},
705726
{
706727
kind: 'ENUM',
@@ -806,6 +827,7 @@ describe('Introspection', () => {
806827
},
807828
],
808829
possibleTypes: null,
830+
specifiedBy: null,
809831
},
810832
],
811833
directives: [
@@ -847,6 +869,25 @@ describe('Introspection', () => {
847869
},
848870
],
849871
},
872+
{
873+
name: 'specified',
874+
locations: ['SCALAR'],
875+
args: [
876+
{
877+
defaultValue: null,
878+
name: 'by',
879+
type: {
880+
kind: 'NON_NULL',
881+
name: null,
882+
ofType: {
883+
kind: 'SCALAR',
884+
name: 'String',
885+
ofType: null,
886+
},
887+
},
888+
},
889+
],
890+
},
850891
{
851892
name: 'deprecated',
852893
locations: ['FIELD_DEFINITION', 'ENUM_VALUE'],

src/type/definition.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ export interface GraphQLObjectTypeConfig<
409409
> {
410410
name: string;
411411
description?: Maybe<string>;
412+
specifiedBy?: string;
412413
interfaces?: Thunk<Maybe<GraphQLInterfaceType[]>>;
413414
fields: Thunk<GraphQLFieldConfigMap<TSource, TContext, TArgs>>;
414415
isTypeOf?: Maybe<GraphQLIsTypeOfFn<TSource, TContext>>;

src/type/directives.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export const GraphQLIncludeDirective: GraphQLDirective;
5454
*/
5555
export const GraphQLSkipDirective: GraphQLDirective;
5656

57+
/**
58+
* Used to provide an RFC3986-compliant URI for specifying the behaviour of
59+
* custom scalar definitions.
60+
*/
61+
export const GraphQLSpecifiedDirective: GraphQLDirective;
62+
5763
/**
5864
* Constant string used for default reason for a deprecation.
5965
*/

src/type/directives.js

+16
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,21 @@ export const GraphQLSkipDirective = new GraphQLDirective({
167167
},
168168
});
169169

170+
/**
171+
* Used to provide URL for specifying the behaviour of custom scalar definitions.
172+
*/
173+
export const GraphQLSpecifiedDirective = new GraphQLDirective({
174+
name: 'specified',
175+
description: 'Exposes a URL that specifies the behaviour of this scalar.',
176+
locations: [DirectiveLocation.SCALAR],
177+
args: {
178+
by: {
179+
type: GraphQLNonNull(GraphQLString),
180+
description: 'The URL that specifies the behaviour of this scalar.',
181+
},
182+
},
183+
});
184+
170185
/**
171186
* Constant string used for default reason for a deprecation.
172187
*/
@@ -195,6 +210,7 @@ export const GraphQLDeprecatedDirective = new GraphQLDirective({
195210
export const specifiedDirectives = Object.freeze([
196211
GraphQLIncludeDirective,
197212
GraphQLSkipDirective,
213+
GraphQLSpecifiedDirective,
198214
GraphQLDeprecatedDirective,
199215
]);
200216

src/type/introspection.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import invariant from '../jsutils/invariant';
88
import { print } from '../language/printer';
99
import { DirectiveLocation } from '../language/directiveLocation';
1010
import { astFromValue } from '../utilities/astFromValue';
11+
import { getDirectiveValues } from '../execution/values';
1112

1213
import { type GraphQLSchema } from './schema';
13-
import { type GraphQLDirective } from './directives';
14+
import { type GraphQLDirective, GraphQLSpecifiedDirective } from './directives';
1415
import { GraphQLString, GraphQLBoolean } from './scalars';
1516
import {
1617
type GraphQLType,
@@ -184,7 +185,7 @@ export const __DirectiveLocation = new GraphQLEnumType({
184185
export const __Type = new GraphQLObjectType({
185186
name: '__Type',
186187
description:
187-
'The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.',
188+
'The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional specifiedBy URL, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.',
188189
fields: () =>
189190
({
190191
kind: {
@@ -221,6 +222,20 @@ export const __Type = new GraphQLObjectType({
221222
resolve: obj =>
222223
obj.description !== undefined ? obj.description : undefined,
223224
},
225+
specifiedBy: {
226+
type: GraphQLString,
227+
resolve: type => {
228+
if (!isScalarType(type) || !type.astNode) {
229+
return null;
230+
}
231+
232+
const specified = getDirectiveValues(
233+
GraphQLSpecifiedDirective,
234+
type.astNode,
235+
);
236+
return specified && specified.by;
237+
},
238+
},
224239
fields: {
225240
type: GraphQLList(GraphQLNonNull(__Field)),
226241
args: {

src/utilities/__tests__/buildASTSchema-test.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
assertDirective,
1616
GraphQLSkipDirective,
1717
GraphQLIncludeDirective,
18+
GraphQLSpecifiedDirective,
1819
GraphQLDeprecatedDirective,
1920
} from '../../type/directives';
2021
import {
@@ -182,12 +183,15 @@ describe('Schema Builder', () => {
182183
expect(cycleSDL(sdl, { commentDescriptions: true })).to.equal(sdl);
183184
});
184185

185-
it('Maintains @skip & @include', () => {
186+
it('Maintains @include, @skip & @specified', () => {
186187
const schema = buildSchema('type Query');
187188

188-
expect(schema.getDirectives()).to.have.lengthOf(3);
189+
expect(schema.getDirectives()).to.have.lengthOf(4);
189190
expect(schema.getDirective('skip')).to.equal(GraphQLSkipDirective);
190191
expect(schema.getDirective('include')).to.equal(GraphQLIncludeDirective);
192+
expect(schema.getDirective('specified')).to.equal(
193+
GraphQLSpecifiedDirective,
194+
);
191195
expect(schema.getDirective('deprecated')).to.equal(
192196
GraphQLDeprecatedDirective,
193197
);
@@ -197,27 +201,32 @@ describe('Schema Builder', () => {
197201
const schema = buildSchema(`
198202
directive @skip on FIELD
199203
directive @include on FIELD
204+
directive @specified on FIELD_DEFINITION
200205
directive @deprecated on FIELD_DEFINITION
201206
`);
202207

203-
expect(schema.getDirectives()).to.have.lengthOf(3);
208+
expect(schema.getDirectives()).to.have.lengthOf(4);
204209
expect(schema.getDirective('skip')).to.not.equal(GraphQLSkipDirective);
205210
expect(schema.getDirective('include')).to.not.equal(
206211
GraphQLIncludeDirective,
207212
);
213+
expect(schema.getDirective('specified')).to.not.equal(
214+
GraphQLSpecifiedDirective,
215+
);
208216
expect(schema.getDirective('deprecated')).to.not.equal(
209217
GraphQLDeprecatedDirective,
210218
);
211219
});
212220

213-
it('Adding directives maintains @skip & @include', () => {
221+
it('Adding directives maintains @include, @skip & @specified', () => {
214222
const schema = buildSchema(`
215223
directive @foo(arg: Int) on FIELD
216224
`);
217225

218-
expect(schema.getDirectives()).to.have.lengthOf(4);
226+
expect(schema.getDirectives()).to.have.lengthOf(5);
219227
expect(schema.getDirective('skip')).to.not.equal(undefined);
220228
expect(schema.getDirective('include')).to.not.equal(undefined);
229+
expect(schema.getDirective('specified')).to.not.equal(undefined);
221230
expect(schema.getDirective('deprecated')).to.not.equal(undefined);
222231
});
223232

src/utilities/__tests__/findBreakingChanges-test.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { GraphQLSchema } from '../../type/schema';
77
import {
88
GraphQLSkipDirective,
99
GraphQLIncludeDirective,
10+
GraphQLSpecifiedDirective,
1011
GraphQLDeprecatedDirective,
1112
} from '../../type/directives';
1213

@@ -790,7 +791,11 @@ describe('findBreakingChanges', () => {
790791
const oldSchema = new GraphQLSchema({});
791792

792793
const newSchema = new GraphQLSchema({
793-
directives: [GraphQLSkipDirective, GraphQLIncludeDirective],
794+
directives: [
795+
GraphQLSkipDirective,
796+
GraphQLIncludeDirective,
797+
GraphQLSpecifiedDirective,
798+
],
794799
});
795800

796801
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([

src/utilities/__tests__/schemaPrinter-test.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,12 @@ describe('Type System Printer', () => {
590590
if: Boolean!
591591
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
592592
593+
"""Exposes a URL that specifies the behaviour of this scalar."""
594+
directive @specified(
595+
"""The URL that specifies the behaviour of this scalar."""
596+
by: String!
597+
) on SCALAR
598+
593599
"""Marks an element of a GraphQL schema as no longer supported."""
594600
directive @deprecated(
595601
"""
@@ -735,12 +741,13 @@ describe('Type System Printer', () => {
735741
"""
736742
The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the \`__TypeKind\` enum.
737743
738-
Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.
744+
Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional specifiedBy URL, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.
739745
"""
740746
type __Type {
741747
kind: __TypeKind!
742748
name: String
743749
description: String
750+
specifiedBy: String
744751
fields(includeDeprecated: Boolean = false): [__Field!]
745752
interfaces: [__Type!]
746753
possibleTypes: [__Type!]
@@ -803,6 +810,12 @@ describe('Type System Printer', () => {
803810
if: Boolean!
804811
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
805812
813+
# Exposes a URL that specifies the behaviour of this scalar.
814+
directive @specified(
815+
# The URL that specifies the behaviour of this scalar.
816+
by: String!
817+
) on SCALAR
818+
806819
# Marks an element of a GraphQL schema as no longer supported.
807820
directive @deprecated(
808821
# Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).
@@ -927,11 +940,12 @@ describe('Type System Printer', () => {
927940
928941
# The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the \`__TypeKind\` enum.
929942
#
930-
# Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.
943+
# Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional specifiedBy URL, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.
931944
type __Type {
932945
kind: __TypeKind!
933946
name: String
934947
description: String
948+
specifiedBy: String
935949
fields(includeDeprecated: Boolean = false): [__Field!]
936950
interfaces: [__Type!]
937951
possibleTypes: [__Type!]

src/utilities/buildASTSchema.js

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
GraphQLSkipDirective,
5656
GraphQLIncludeDirective,
5757
GraphQLDeprecatedDirective,
58+
GraphQLSpecifiedDirective,
5859
} from '../type/directives';
5960
import {
6061
type GraphQLType,
@@ -170,6 +171,10 @@ export function buildASTSchema(
170171
directives.push(GraphQLIncludeDirective);
171172
}
172173

174+
if (!directives.some(directive => directive.name === 'specified')) {
175+
directives.push(GraphQLSpecifiedDirective);
176+
}
177+
173178
if (!directives.some(directive => directive.name === 'deprecated')) {
174179
directives.push(GraphQLDeprecatedDirective);
175180
}

src/utilities/getIntrospectionQuery.js

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function getIntrospectionQuery(options?: IntrospectionOptions): string {
3434
kind
3535
name
3636
${descriptions ? 'description' : ''}
37+
specifiedBy
3738
fields(includeDeprecated: true) {
3839
name
3940
${descriptions ? 'description' : ''}

0 commit comments

Comments
 (0)