Skip to content

Commit

Permalink
Update @specified directive implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
m14t committed Dec 16, 2019
1 parent 4acf15a commit 8bb758d
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/APIReference-TypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ type GraphQLScalarTypeConfig<InternalType> = {
serialize: (value: mixed) => ?InternalType;
parseValue?: (value: mixed) => ?InternalType;
parseLiteral?: (valueAST: Value) => ?InternalType;
specifiedBy?: string;
}
```
Expand Down
23 changes: 23 additions & 0 deletions src/type/__tests__/definition-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ describe('Type System: Scalars', () => {
expect(() => new GraphQLScalarType({ name: 'SomeScalar' })).not.to.throw();
});

it('accepts a Scalar type defining specifiedBy', () => {
expect(
() =>
new GraphQLScalarType({
name: 'SomeScalar',
specifiedBy: 'https://tools.ietf.org/html/rfc4122',
}),
).not.to.throw();
});

it('accepts a Scalar type defining parseValue and parseLiteral', () => {
expect(
() =>
Expand Down Expand Up @@ -118,6 +128,19 @@ describe('Type System: Scalars', () => {
'SomeScalar must provide both "parseValue" and "parseLiteral" functions.',
);
});

it('rejects a Scalar type defining specifiedBy with an incorrect type', () => {
expect(
() =>
new GraphQLScalarType({
name: 'SomeScalar',
// $DisableFlowOnNegativeTest
specifiedBy: {},
}),
).to.throw(
'SomeScalar must provide "specifiedBy" as a string, but got: {}.',
);
});
});

describe('Type System: Objects', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/type/definition.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export class GraphQLScalarType {
extensions: Maybe<Readonly<Record<string, any>>>;
astNode: Maybe<ScalarTypeDefinitionNode>;
extensionASTNodes: Maybe<ReadonlyArray<ScalarTypeExtensionNode>>;
specifiedBy?: Maybe<string>;

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

Expand All @@ -300,6 +301,7 @@ export class GraphQLScalarType {
parseLiteral: GraphQLScalarLiteralParser<any>;
extensions: Maybe<Readonly<Record<string, any>>>;
extensionASTNodes: ReadonlyArray<ScalarTypeExtensionNode>;
specifiedBy: Maybe<string>;
};

toString(): string;
Expand Down Expand Up @@ -330,6 +332,7 @@ export interface GraphQLScalarTypeConfig<TInternal, TExternal> {
extensions?: Maybe<Readonly<Record<string, any>>>;
astNode?: Maybe<ScalarTypeDefinitionNode>;
extensionASTNodes?: Maybe<ReadonlyArray<ScalarTypeExtensionNode>>;
specifiedBy?: Maybe<string>;
}

/**
Expand Down Expand Up @@ -409,7 +412,6 @@ export interface GraphQLObjectTypeConfig<
> {
name: string;
description?: Maybe<string>;
specifiedBy?: string;
interfaces?: Thunk<Maybe<GraphQLInterfaceType[]>>;
fields: Thunk<GraphQLFieldConfigMap<TSource, TContext, TArgs>>;
isTypeOf?: Maybe<GraphQLIsTypeOfFn<TSource, TContext>>;
Expand Down
12 changes: 12 additions & 0 deletions src/type/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ export class GraphQLScalarType {
extensions: ?ReadOnlyObjMap<mixed>;
astNode: ?ScalarTypeDefinitionNode;
extensionASTNodes: ?$ReadOnlyArray<ScalarTypeExtensionNode>;
specifiedBy: ?string;

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

devAssert(typeof config.name === 'string', 'Must provide name.');
devAssert(
Expand All @@ -578,6 +580,14 @@ export class GraphQLScalarType {
`${this.name} must provide both "parseValue" and "parseLiteral" functions.`,
);
}

if (config.specifiedBy) {
devAssert(
typeof config.specifiedBy === 'string',
`${this.name} must provide "specifiedBy" as a string, ` +
`but got: ${inspect(config.specifiedBy)}.`,
);
}
}

toConfig(): {|
Expand All @@ -587,6 +597,7 @@ export class GraphQLScalarType {
parseLiteral: GraphQLScalarLiteralParser<*>,
extensions: ?ReadOnlyObjMap<mixed>,
extensionASTNodes: ?$ReadOnlyArray<ScalarTypeExtensionNode>,
specifiedBy: ?string,
|} {
return {
name: this.name,
Expand All @@ -597,6 +608,7 @@ export class GraphQLScalarType {
extensions: this.extensions,
astNode: this.astNode,
extensionASTNodes: this.extensionASTNodes,
specifiedBy: this.specifiedBy,
};
}

Expand Down
16 changes: 3 additions & 13 deletions src/type/introspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import invariant from '../jsutils/invariant';
import { print } from '../language/printer';
import { DirectiveLocation } from '../language/directiveLocation';
import { astFromValue } from '../utilities/astFromValue';
import { getDirectiveValues } from '../execution/values';

import { type GraphQLSchema } from './schema';
import { type GraphQLDirective, GraphQLSpecifiedDirective } from './directives';
import { type GraphQLDirective } from './directives';
import { GraphQLString, GraphQLBoolean } from './scalars';
import {
type GraphQLType,
Expand Down Expand Up @@ -224,17 +223,8 @@ export const __Type = new GraphQLObjectType({
},
specifiedBy: {
type: GraphQLString,
resolve: type => {
if (!isScalarType(type) || !type.astNode) {
return null;
}

const specified = getDirectiveValues(
GraphQLSpecifiedDirective,
type.astNode,
);
return specified && specified.by;
},
resolve: obj =>
obj.specifiedBy !== undefined ? obj.specifiedBy : undefined,
},
fields: {
type: GraphQLList(GraphQLNonNull(__Field)),
Expand Down
19 changes: 19 additions & 0 deletions src/utilities/__tests__/buildASTSchema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,25 @@ describe('Schema Builder', () => {
});
});

it('Supports @specified', () => {
const sdl = dedent`
type Query {
someUUID: UUID @deprecated
}
scalar UUID @specified(by: "https://tools.ietf.org/html/rfc4122")
`;
expect(cycleSDL(sdl)).to.equal(sdl);

const schema = buildSchema(sdl);

const uuid = assertScalarType(schema.getType('UUID'));

expect(uuid).to.include({
specifiedBy: 'https://tools.ietf.org/html/rfc4122',
});
});

it('Correctly assign AST nodes', () => {
const sdl = dedent`
schema {
Expand Down
12 changes: 12 additions & 0 deletions src/utilities/__tests__/buildClientSchema-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,18 @@ describe('Type System: build schema from introspection', () => {
expect(cycleIntrospection(sdl)).to.equal(sdl);
});

it('builds a schema with specified by', () => {
const sdl = dedent`
type Query {
someUUID: UUID
}
scalar UUID @specified(by: "https://tools.ietf.org/html/rfc4122")
`;

expect(cycleIntrospection(sdl)).to.equal(sdl);
});

it('can use client schema for limited execution', () => {
const schema = buildSchema(`
scalar CustomScalar
Expand Down
9 changes: 9 additions & 0 deletions src/utilities/buildASTSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ export class ASTDefinitionBuilder {
name,
description,
astNode,
specifiedBy: getSpecifiedBy(astNode),
});
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
return new GraphQLInputObjectType({
Expand Down Expand Up @@ -475,6 +476,14 @@ function getDeprecationReason(
return deprecated && (deprecated.reason: any);
}

/**
* Given a scalar node, returns the string value for specifiedBy string.
*/
function getSpecifiedBy(node: ScalarTypeDefinitionNode): ?string {
const specified = getDirectiveValues(GraphQLSpecifiedDirective, node);
return specified && (specified.by: any);
}

/**
* Given an ast node, returns its string description.
* @deprecated: provided to ease adoption and will be removed in v16.
Expand Down
1 change: 1 addition & 0 deletions src/utilities/buildClientSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export function buildClientSchema(
return new GraphQLScalarType({
name: scalarIntrospection.name,
description: scalarIntrospection.description,
specifiedBy: scalarIntrospection.specifiedBy,
});
}

Expand Down
1 change: 1 addition & 0 deletions src/utilities/getIntrospectionQuery.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface IntrospectionScalarType {
readonly kind: 'SCALAR';
readonly name: string;
readonly description?: Maybe<string>;
readonly specifiedBy?: Maybe<string>;
}

export interface IntrospectionObjectType {
Expand Down
1 change: 1 addition & 0 deletions src/utilities/getIntrospectionQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export type IntrospectionScalarType = {|
+kind: 'SCALAR',
+name: string,
+description?: ?string,
+specifiedBy: ?string,
|};

export type IntrospectionObjectType = {|
Expand Down
15 changes: 14 additions & 1 deletion src/utilities/schemaPrinter.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,11 @@ export function printType(type: GraphQLNamedType, options?: Options): string {
}

function printScalar(type: GraphQLScalarType, options): string {
return printDescription(options, type) + `scalar ${type.name}`;
return (
printDescription(options, type) +
`scalar ${type.name}` +
printSpecifiedBy(type)
);
}

function printImplementedInterfaces(
Expand Down Expand Up @@ -317,6 +321,15 @@ function printDeprecated(fieldOrEnumVal) {
return ' @deprecated';
}

function printSpecifiedBy(scalar) {
if (!scalar.specifiedBy) {
return '';
}
const by = scalar.specifiedBy;
const byAST = astFromValue(by, GraphQLString);
return ' @specified(by: ' + print(byAST) + ')';
}

function printDescription(
options,
def,
Expand Down

0 comments on commit 8bb758d

Please sign in to comment.