Skip to content

Commit e76581b

Browse files
committed
Update @specified directive implementation
1 parent 4acf15a commit e76581b

12 files changed

+107
-15
lines changed

docs/APIReference-TypeSystem.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ type GraphQLScalarTypeConfig<InternalType> = {
209209
serialize: (value: mixed) => ?InternalType;
210210
parseValue?: (value: mixed) => ?InternalType;
211211
parseLiteral?: (valueAST: Value) => ?InternalType;
212+
specifiedBy?: string;
212213
}
213214
```
214215

src/type/__tests__/definition-test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ describe('Type System: Scalars', () => {
4444
expect(() => new GraphQLScalarType({ name: 'SomeScalar' })).not.to.throw();
4545
});
4646

47+
it('accepts a Scalar type defining specifiedBy', () => {
48+
expect(
49+
() =>
50+
new GraphQLScalarType({
51+
name: 'SomeScalar',
52+
specifiedBy: 'https://tools.ietf.org/html/rfc4122',
53+
}),
54+
).not.to.throw();
55+
});
56+
4757
it('accepts a Scalar type defining parseValue and parseLiteral', () => {
4858
expect(
4959
() =>
@@ -118,6 +128,19 @@ describe('Type System: Scalars', () => {
118128
'SomeScalar must provide both "parseValue" and "parseLiteral" functions.',
119129
);
120130
});
131+
132+
it('rejects a Scalar type defining specifiedBy with an incorrect type', () => {
133+
expect(
134+
() =>
135+
new GraphQLScalarType({
136+
name: 'SomeScalar',
137+
// $DisableFlowOnNegativeTest
138+
specifiedBy: {},
139+
}),
140+
).to.throw(
141+
'SomeScalar must provide "specifiedBy" as a string, but got: {}.',
142+
);
143+
});
121144
});
122145

123146
describe('Type System: Objects', () => {

src/type/definition.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ export class GraphQLScalarType {
291291
extensions: Maybe<Readonly<Record<string, any>>>;
292292
astNode: Maybe<ScalarTypeDefinitionNode>;
293293
extensionASTNodes: Maybe<ReadonlyArray<ScalarTypeExtensionNode>>;
294+
specifiedBy?: Maybe<string>;
294295

295296
constructor(config: GraphQLScalarTypeConfig<any, any>);
296297

@@ -300,6 +301,7 @@ export class GraphQLScalarType {
300301
parseLiteral: GraphQLScalarLiteralParser<any>;
301302
extensions: Maybe<Readonly<Record<string, any>>>;
302303
extensionASTNodes: ReadonlyArray<ScalarTypeExtensionNode>;
304+
specifiedBy: Maybe<string>;
303305
};
304306

305307
toString(): string;
@@ -330,6 +332,7 @@ export interface GraphQLScalarTypeConfig<TInternal, TExternal> {
330332
extensions?: Maybe<Readonly<Record<string, any>>>;
331333
astNode?: Maybe<ScalarTypeDefinitionNode>;
332334
extensionASTNodes?: Maybe<ReadonlyArray<ScalarTypeExtensionNode>>;
335+
specifiedBy?: Maybe<string>;
333336
}
334337

335338
/**
@@ -409,7 +412,6 @@ export interface GraphQLObjectTypeConfig<
409412
> {
410413
name: string;
411414
description?: Maybe<string>;
412-
specifiedBy?: string;
413415
interfaces?: Thunk<Maybe<GraphQLInterfaceType[]>>;
414416
fields: Thunk<GraphQLFieldConfigMap<TSource, TContext, TArgs>>;
415417
isTypeOf?: Maybe<GraphQLIsTypeOfFn<TSource, TContext>>;

src/type/definition.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ export class GraphQLScalarType {
552552
extensions: ?ReadOnlyObjMap<mixed>;
553553
astNode: ?ScalarTypeDefinitionNode;
554554
extensionASTNodes: ?$ReadOnlyArray<ScalarTypeExtensionNode>;
555+
specifiedBy: ?string;
555556

556557
constructor(config: GraphQLScalarTypeConfig<*, *>): void {
557558
const parseValue = config.parseValue || identityFunc;
@@ -564,6 +565,7 @@ export class GraphQLScalarType {
564565
this.extensions = config.extensions && toObjMap(config.extensions);
565566
this.astNode = config.astNode;
566567
this.extensionASTNodes = undefineIfEmpty(config.extensionASTNodes);
568+
this.specifiedBy = config.specifiedBy;
567569

568570
devAssert(typeof config.name === 'string', 'Must provide name.');
569571
devAssert(
@@ -578,6 +580,14 @@ export class GraphQLScalarType {
578580
`${this.name} must provide both "parseValue" and "parseLiteral" functions.`,
579581
);
580582
}
583+
584+
if (config.specifiedBy !== undefined) {
585+
devAssert(
586+
typeof config.specifiedBy === 'string',
587+
`${this.name} must provide "specifiedBy" as a string, ` +
588+
`but got: ${inspect(config.specifiedBy)}.`,
589+
);
590+
}
581591
}
582592

583593
toConfig(): {|
@@ -587,6 +597,7 @@ export class GraphQLScalarType {
587597
parseLiteral: GraphQLScalarLiteralParser<*>,
588598
extensions: ?ReadOnlyObjMap<mixed>,
589599
extensionASTNodes: ?$ReadOnlyArray<ScalarTypeExtensionNode>,
600+
specifiedBy: ?string,
590601
|} {
591602
return {
592603
name: this.name,
@@ -597,6 +608,7 @@ export class GraphQLScalarType {
597608
extensions: this.extensions,
598609
astNode: this.astNode,
599610
extensionASTNodes: this.extensionASTNodes,
611+
specifiedBy: this.specifiedBy,
600612
};
601613
}
602614

@@ -628,6 +640,7 @@ export type GraphQLScalarTypeConfig<TInternal, TExternal> = {|
628640
extensions?: ?ReadOnlyObjMapLike<mixed>,
629641
astNode?: ?ScalarTypeDefinitionNode,
630642
extensionASTNodes?: ?$ReadOnlyArray<ScalarTypeExtensionNode>,
643+
specifiedBy?: ?string,
631644
|};
632645

633646
/**

src/type/introspection.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ 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';
1211

1312
import { type GraphQLSchema } from './schema';
14-
import { type GraphQLDirective, GraphQLSpecifiedDirective } from './directives';
13+
import { type GraphQLDirective } from './directives';
1514
import { GraphQLString, GraphQLBoolean } from './scalars';
1615
import {
1716
type GraphQLType,
@@ -224,17 +223,8 @@ export const __Type = new GraphQLObjectType({
224223
},
225224
specifiedBy: {
226225
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-
},
226+
resolve: obj =>
227+
obj.specifiedBy !== undefined ? obj.specifiedBy : undefined,
238228
},
239229
fields: {
240230
type: GraphQLList(GraphQLNonNull(__Field)),

src/utilities/__tests__/buildASTSchema-test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,25 @@ describe('Schema Builder', () => {
746746
});
747747
});
748748

749+
it('Supports @specified', () => {
750+
const sdl = dedent`
751+
type Query {
752+
someUUID: UUID @deprecated
753+
}
754+
755+
scalar UUID @specified(by: "https://tools.ietf.org/html/rfc4122")
756+
`;
757+
expect(cycleSDL(sdl)).to.equal(sdl);
758+
759+
const schema = buildSchema(sdl);
760+
761+
const uuid = assertScalarType(schema.getType('UUID'));
762+
763+
expect(uuid).to.include({
764+
specifiedBy: 'https://tools.ietf.org/html/rfc4122',
765+
});
766+
});
767+
749768
it('Correctly assign AST nodes', () => {
750769
const sdl = dedent`
751770
schema {

src/utilities/__tests__/buildClientSchema-test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,18 @@ describe('Type System: build schema from introspection', () => {
530530
expect(cycleIntrospection(sdl)).to.equal(sdl);
531531
});
532532

533+
it('builds a schema with specified by', () => {
534+
const sdl = dedent`
535+
type Query {
536+
someUUID: UUID
537+
}
538+
539+
scalar UUID @specified(by: "https://tools.ietf.org/html/rfc4122")
540+
`;
541+
542+
expect(cycleIntrospection(sdl)).to.equal(sdl);
543+
});
544+
533545
it('can use client schema for limited execution', () => {
534546
const schema = buildSchema(`
535547
scalar CustomScalar

src/utilities/buildASTSchema.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
type DocumentNode,
2222
type TypeNode,
2323
type NamedTypeNode,
24+
type ScalarTypeDefinitionNode,
2425
type SchemaDefinitionNode,
2526
type SchemaExtensionNode,
2627
type TypeDefinitionNode,
@@ -446,6 +447,7 @@ export class ASTDefinitionBuilder {
446447
name,
447448
description,
448449
astNode,
450+
specifiedBy: getSpecifiedBy(astNode),
449451
});
450452
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
451453
return new GraphQLInputObjectType({
@@ -475,6 +477,14 @@ function getDeprecationReason(
475477
return deprecated && (deprecated.reason: any);
476478
}
477479

480+
/**
481+
* Given a scalar node, returns the string value for specifiedBy string.
482+
*/
483+
function getSpecifiedBy(node: ScalarTypeDefinitionNode): ?string {
484+
const specified = getDirectiveValues(GraphQLSpecifiedDirective, node);
485+
return specified && (specified.by: any);
486+
}
487+
478488
/**
479489
* Given an ast node, returns its string description.
480490
* @deprecated: provided to ease adoption and will be removed in v16.

src/utilities/buildClientSchema.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ export function buildClientSchema(
229229
return new GraphQLScalarType({
230230
name: scalarIntrospection.name,
231231
description: scalarIntrospection.description,
232+
specifiedBy: scalarIntrospection.specifiedBy,
232233
});
233234
}
234235

src/utilities/getIntrospectionQuery.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface IntrospectionScalarType {
4949
readonly kind: 'SCALAR';
5050
readonly name: string;
5151
readonly description?: Maybe<string>;
52+
readonly specifiedBy?: Maybe<string>;
5253
}
5354

5455
export interface IntrospectionObjectType {

0 commit comments

Comments
 (0)