From 984f5ad5eea0cbdefb8ec3db11bf8f760f643984 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Wed, 13 Jun 2018 13:49:33 +0200 Subject: [PATCH] extendSchema: add support for extending scalars --- src/type/definition.js | 4 +++ src/utilities/__tests__/extendSchema-test.js | 28 +++++++++++++++- src/utilities/extendSchema.js | 34 ++++++++++++++++---- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/type/definition.js b/src/type/definition.js index 2d668a26c2..f3672fdcb6 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -26,6 +26,7 @@ import type { EnumTypeDefinitionNode, EnumValueDefinitionNode, InputObjectTypeDefinitionNode, + ScalarTypeExtensionNode, ObjectTypeExtensionNode, InterfaceTypeExtensionNode, UnionTypeExtensionNode, @@ -535,6 +536,7 @@ export class GraphQLScalarType { name: string; description: ?string; astNode: ?ScalarTypeDefinitionNode; + extensionASTNodes: ?$ReadOnlyArray; _scalarConfig: GraphQLScalarTypeConfig<*, *>; @@ -542,6 +544,7 @@ export class GraphQLScalarType { this.name = config.name; this.description = config.description; this.astNode = config.astNode; + this.extensionASTNodes = config.extensionASTNodes; this._scalarConfig = config; invariant(typeof config.name === 'string', 'Must provide name.'); invariant( @@ -593,6 +596,7 @@ export type GraphQLScalarTypeConfig = { name: string, description?: ?string, astNode?: ?ScalarTypeDefinitionNode, + extensionASTNodes?: ?$ReadOnlyArray, serialize: (value: mixed) => ?TExternal, parseValue?: (value: mixed) => ?TInternal, parseLiteral?: ( diff --git a/src/utilities/__tests__/extendSchema-test.js b/src/utilities/__tests__/extendSchema-test.js index f962393a97..1ecce9a8da 100644 --- a/src/utilities/__tests__/extendSchema-test.js +++ b/src/utilities/__tests__/extendSchema-test.js @@ -15,6 +15,7 @@ import { Kind } from '../../language/kinds'; import { graphqlSync } from '../../'; import { GraphQLSchema, + GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, @@ -32,6 +33,11 @@ import { } from '../../type'; // Test schema. +const SomeScalarType = new GraphQLScalarType({ + name: 'SomeScalar', + serialize: x => x, +}); + const SomeInterfaceType = new GraphQLInterfaceType({ name: 'SomeInterface', fields: () => ({ @@ -92,6 +98,7 @@ const testSchema = new GraphQLSchema({ name: 'Query', fields: () => ({ foo: { type: FooType }, + someScalar: { type: SomeScalarType }, someUnion: { type: SomeUnionType }, someEnum: { type: SomeEnumType }, someInterface: { @@ -294,12 +301,26 @@ describe('extendSchema', () => { expect(fooDirective.args[0].type).to.equal(someInputType); }); + it('extends scalars by adding new directives', () => { + const extendedSchema = extendTestSchema(` + extend scalar SomeScalar @foo + `); + + const someScalar = extendedSchema.getType('SomeScalar'); + expect(someScalar.extensionASTNodes).to.have.lengthOf(1); + expect(print(someScalar.extensionASTNodes[0])).to.equal( + 'extend scalar SomeScalar @foo', + ); + }); + it('correctly assign AST nodes to new and extended types', () => { const extendedSchema = extendTestSchema(` extend type Query { newField(testArg: TestInput): TestEnum } + extend scalar SomeScalar @foo + extend enum SomeEnum { NEW_VALUE } @@ -327,6 +348,8 @@ describe('extendSchema', () => { oneMoreNewField: TestUnion } + extend scalar SomeScalar @test + extend enum SomeEnum { ONE_MORE_NEW_VALUE } @@ -351,11 +374,12 @@ describe('extendSchema', () => { interfaceField: String } - directive @test(arg: Int) on FIELD + directive @test(arg: Int) on FIELD | SCALAR `); const extendedTwiceSchema = extendSchema(extendedSchema, ast); const query = extendedTwiceSchema.getType('Query'); + const someScalar = extendedTwiceSchema.getType('SomeScalar'); const someEnum = extendedTwiceSchema.getType('SomeEnum'); const someUnion = extendedTwiceSchema.getType('SomeUnion'); const someInput = extendedTwiceSchema.getType('SomeInput'); @@ -369,6 +393,7 @@ describe('extendSchema', () => { const testDirective = extendedTwiceSchema.getDirective('test'); expect(query.extensionASTNodes).to.have.lengthOf(2); + expect(someScalar.extensionASTNodes).to.have.lengthOf(2); expect(someEnum.extensionASTNodes).to.have.lengthOf(2); expect(someUnion.extensionASTNodes).to.have.lengthOf(2); expect(someInput.extensionASTNodes).to.have.lengthOf(2); @@ -384,6 +409,7 @@ describe('extendSchema', () => { kind: Kind.DOCUMENT, definitions: [ ...query.extensionASTNodes, + ...someScalar.extensionASTNodes, ...someEnum.extensionASTNodes, ...someUnion.extensionASTNodes, ...someInput.extensionASTNodes, diff --git a/src/utilities/extendSchema.js b/src/utilities/extendSchema.js index ae4d439722..9f410c70b7 100644 --- a/src/utilities/extendSchema.js +++ b/src/utilities/extendSchema.js @@ -15,6 +15,7 @@ import { ASTDefinitionBuilder } from './buildASTSchema'; import { GraphQLError } from '../error/GraphQLError'; import { isSchema, GraphQLSchema } from '../type/schema'; import { isIntrospectionType } from '../type/introspection'; +import { isSpecifiedScalarType } from '../type/scalars'; import type { GraphQLSchemaValidationOptions } from '../type/schema'; @@ -24,6 +25,7 @@ import type { } from '../type/definition'; import { + isScalarType, isObjectType, isInterfaceType, isUnionType, @@ -33,6 +35,7 @@ import { isInputObjectType, GraphQLList, GraphQLNonNull, + GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, @@ -135,6 +138,7 @@ export function extendSchema( } typeDefinitionMap[typeName] = def; break; + case Kind.SCALAR_TYPE_EXTENSION: case Kind.OBJECT_TYPE_EXTENSION: case Kind.INTERFACE_TYPE_EXTENSION: case Kind.ENUM_TYPE_EXTENSION: @@ -170,10 +174,6 @@ export function extendSchema( } directiveDefinitions.push(def); break; - case Kind.SCALAR_TYPE_EXTENSION: - throw new Error( - `The ${def.kind} kind is not yet supported by extendSchema().`, - ); } } @@ -280,14 +280,16 @@ export function extendSchema( } function extendNamedType(type: T): T { - if (isIntrospectionType(type)) { - // Introspection types are not extended. + if (isIntrospectionType(type) || isSpecifiedScalarType(type)) { + // Builtin types are not extended. return type; } const name = type.name; if (!extendTypeCache[name]) { - if (isObjectType(type)) { + if (isScalarType(type)) { + extendTypeCache[name] = extendScalarType(type); + } else if (isObjectType(type)) { extendTypeCache[name] = extendObjectType(type); } else if (isInterfaceType(type)) { extendTypeCache[name] = extendInterfaceType(type); @@ -425,6 +427,24 @@ export function extendSchema( return newValueMap; } + function extendScalarType(type: GraphQLScalarType): GraphQLScalarType { + const name = type.name; + const extensionASTNodes = typeExtensionsMap[name] + ? type.extensionASTNodes + ? type.extensionASTNodes.concat(typeExtensionsMap[name]) + : typeExtensionsMap[name] + : type.extensionASTNodes; + return new GraphQLScalarType({ + name, + description: type.description, + astNode: type.astNode, + extensionASTNodes, + serialize: type._scalarConfig.serialize, + parseValue: type._scalarConfig.parseValue, + parseLiteral: type._scalarConfig.parseLiteral, + }); + } + function extendObjectType(type: GraphQLObjectType): GraphQLObjectType { const name = type.name; const extensionASTNodes = typeExtensionsMap[name]