From 61ed701899bdd2748f07d5e1d0acc183c466e91e Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Thu, 15 Jun 2017 18:33:07 -0700 Subject: [PATCH 1/2] RFC: Define custom scalars in terms of built-in scalars. This proposes an additive change which allows custom scalars to be defined in terms of the built-in scalars. The motivation is for client-side code generators to understand how to map between the GraphQL type system and a native type system. As an example, a `URL` custom type may be defined in terms of the built-in scalar `String`. It could define additional serialization and parsing logic, however client tools can know to treat `URL` values as `String`. Presently, we do this by defining these mappings manually on the client, which is difficult to scale, or by giving up and making no assumptions of how the custom types serialize. Another real use case of giving client tools this information is GraphiQL: this change will allow GraphiQL to show more useful errors when a literal of an incorrect kind is provided to a custom scalar. Currently GraphiQL simply accepts all values. To accomplish this, this proposes adding the following: * A new property when defining `GraphQLScalarType` (`ofType`) which asserts that only built-in scalar types are provided. * A second type coercion to guarantee to a client that the serialized values match the `ofType`. * Delegating the `parseLiteral` and `parseValue` functions to those in `ofType` (this enables downstream validation / GraphiQL features) * Exposing `ofType` in the introspection system, and consuming that introspection in `buildClientSchema`. * Adding optional syntax to the SDL, and consuming that in `buildASTSchema` and `extendSchema` as well as in `schemaPrinter`. * Adding a case to `findBreakingChanges` which looks for a scalar's ofType changing. --- .../__tests__/schema-kitchen-sink.graphql | 2 ++ src/language/__tests__/schema-parser-test.js | 1 + src/language/__tests__/schema-printer-test.js | 2 ++ src/language/ast.js | 1 + src/language/parser.js | 7 +++++- src/language/printer.js | 8 ++++--- src/language/visitor.js | 2 +- src/type/__tests__/introspection-test.js | 3 ++- src/type/definition.js | 13 ++++++++--- src/type/introspection.js | 3 ++- src/type/validate.js | 22 ++++++++++++++++++- src/utilities/__tests__/schemaPrinter-test.js | 17 ++++++++++++-- src/utilities/buildASTSchema.js | 4 ++++ src/utilities/buildClientSchema.js | 10 +++++++++ src/utilities/findBreakingChanges.js | 12 ++++++++++ src/utilities/introspectionQuery.js | 1 + src/utilities/schemaPrinter.js | 3 ++- 17 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/language/__tests__/schema-kitchen-sink.graphql b/src/language/__tests__/schema-kitchen-sink.graphql index f94f47c8e5..2a9f8344c2 100644 --- a/src/language/__tests__/schema-kitchen-sink.graphql +++ b/src/language/__tests__/schema-kitchen-sink.graphql @@ -65,6 +65,8 @@ extend union Feed @onUnion scalar CustomScalar +scalar StringEncodedCustomScalar = String + scalar AnnotatedScalar @onScalar extend scalar CustomScalar @onScalar diff --git a/src/language/__tests__/schema-parser-test.js b/src/language/__tests__/schema-parser-test.js index 1c9fe0f144..9eb879ba05 100644 --- a/src/language/__tests__/schema-parser-test.js +++ b/src/language/__tests__/schema-parser-test.js @@ -712,6 +712,7 @@ type Hello { { kind: 'ScalarTypeDefinition', name: nameNode('Hello', { start: 7, end: 12 }), + type: undefined, directives: [], loc: { start: 0, end: 12 }, }, diff --git a/src/language/__tests__/schema-printer-test.js b/src/language/__tests__/schema-printer-test.js index 1bacb8e586..f557d40ef8 100644 --- a/src/language/__tests__/schema-printer-test.js +++ b/src/language/__tests__/schema-printer-test.js @@ -109,6 +109,8 @@ describe('Printer', () => { scalar CustomScalar + scalar StringEncodedCustomScalar = String + scalar AnnotatedScalar @onScalar extend scalar CustomScalar @onScalar diff --git a/src/language/ast.js b/src/language/ast.js index bec5ae3d57..395fd78132 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -448,6 +448,7 @@ export type ScalarTypeDefinitionNode = { +loc?: Location, +description?: StringValueNode, +name: NameNode, + +type?: NamedTypeNode, +directives?: $ReadOnlyArray, }; diff --git a/src/language/parser.js b/src/language/parser.js index afa5d6ecab..4df40d6ce7 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -890,18 +890,23 @@ function parseOperationTypeDefinition( } /** - * ScalarTypeDefinition : Description? scalar Name Directives[Const]? + * ScalarTypeDefinition : Description? scalar Name ScalarOfType? Directives[Const]? + * ScalarOfType : = NamedType */ function parseScalarTypeDefinition(lexer: Lexer<*>): ScalarTypeDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'scalar'); const name = parseName(lexer); + const type = skip(lexer, TokenKind.EQUALS) + ? parseNamedType(lexer) + : undefined; const directives = parseDirectives(lexer, true); return { kind: SCALAR_TYPE_DEFINITION, description, name, + type, directives, loc: loc(lexer, start), }; diff --git a/src/language/printer.js b/src/language/printer.js index 10bb63595a..eb18efac9a 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -110,12 +110,14 @@ const printDocASTReducer = { OperationTypeDefinition: ({ operation, type }) => operation + ': ' + type, - ScalarTypeDefinition: ({ description, name, directives }) => + ScalarTypeDefinition: ({ description, name, type, directives }) => join( - [description, join(['scalar', name, join(directives, ' ')], ' ')], + [ + description, + join(['scalar', name, wrap('= ', type), join(directives, ' ')], ' '), + ], '\n', ), - ObjectTypeDefinition: ({ description, name, diff --git a/src/language/visitor.js b/src/language/visitor.js index 31937548ee..0148c96aae 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -101,7 +101,7 @@ export const QueryDocumentKeys = { SchemaDefinition: ['directives', 'operationTypes'], OperationTypeDefinition: ['type'], - ScalarTypeDefinition: ['description', 'name', 'directives'], + ScalarTypeDefinition: ['description', 'name', 'type', 'directives'], ObjectTypeDefinition: [ 'description', 'name', diff --git a/src/type/__tests__/introspection-test.js b/src/type/__tests__/introspection-test.js index e47aea1081..2c696d32f2 100644 --- a/src/type/__tests__/introspection-test.js +++ b/src/type/__tests__/introspection-test.js @@ -1307,7 +1307,8 @@ describe('Introspection', () => { 'An enum describing what kind of type a given `__Type` is.', enumValues: [ { - description: 'Indicates this type is a scalar.', + description: + 'Indicates this type is a scalar. `ofType` is a valid field.', name: 'SCALAR', }, { diff --git a/src/type/definition.js b/src/type/definition.js index 2e54f99ca2..f17bc90adf 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -450,12 +450,14 @@ export class GraphQLScalarType { name: string; description: ?string; astNode: ?ScalarTypeDefinitionNode; + ofType: ?GraphQLScalarType; _scalarConfig: GraphQLScalarTypeConfig<*, *>; constructor(config: GraphQLScalarTypeConfig<*, *>): void { this.name = config.name; this.description = config.description; + this.ofType = config.ofType || null; this.astNode = config.astNode; this._scalarConfig = config; invariant(typeof config.name === 'string', 'Must provide name.'); @@ -478,12 +480,14 @@ export class GraphQLScalarType { // Serializes an internal value to include in a response. serialize(value: mixed): mixed { const serializer = this._scalarConfig.serialize; - return serializer(value); + const serialized = serializer(value); + return this.ofType ? this.ofType.serialize(serialized) : serialized; } // Parses an externally provided value to use as an input. parseValue(value: mixed): mixed { - const parser = this._scalarConfig.parseValue; + const parser = + this._scalarConfig.parseValue || (this.ofType && this.ofType.parseValue); if (isInvalid(value)) { return undefined; } @@ -492,7 +496,9 @@ export class GraphQLScalarType { // Parses an externally provided literal value to use as an input. parseLiteral(valueNode: ValueNode, variables: ?ObjMap): mixed { - const parser = this._scalarConfig.parseLiteral; + const parser = + this._scalarConfig.parseLiteral || + (this.ofType && this.ofType.parseLiteral); return parser ? parser(valueNode, variables) : valueFromASTUntyped(valueNode, variables); @@ -513,6 +519,7 @@ GraphQLScalarType.prototype.toJSON = GraphQLScalarType.prototype.inspect = export type GraphQLScalarTypeConfig = { name: string, description?: ?string, + ofType?: ?GraphQLScalarType, astNode?: ?ScalarTypeDefinitionNode, serialize: (value: mixed) => ?TExternal, parseValue?: (value: mixed) => ?TInternal, diff --git a/src/type/introspection.js b/src/type/introspection.js index 759f4b8f2b..03df860fe4 100644 --- a/src/type/introspection.js +++ b/src/type/introspection.js @@ -381,7 +381,8 @@ export const __TypeKind = new GraphQLEnumType({ values: { SCALAR: { value: TypeKind.SCALAR, - description: 'Indicates this type is a scalar.', + description: + 'Indicates this type is a scalar. `ofType` is a valid field.', }, OBJECT: { value: TypeKind.OBJECT, diff --git a/src/type/validate.js b/src/type/validate.js index bf6ec8315a..eab5779851 100644 --- a/src/type/validate.js +++ b/src/type/validate.js @@ -8,6 +8,7 @@ */ import { + isScalarType, isObjectType, isInterfaceType, isUnionType, @@ -19,6 +20,7 @@ import { isOutputType, } from './definition'; import type { + GraphQLScalarType, GraphQLObjectType, GraphQLInterfaceType, GraphQLUnionType, @@ -30,6 +32,7 @@ import type { GraphQLDirective } from './directives'; import { isIntrospectionType } from './introspection'; import { isSchema } from './schema'; import type { GraphQLSchema } from './schema'; +import { isSpecifiedScalarType } from './scalars'; import find from '../jsutils/find'; import invariant from '../jsutils/invariant'; import { GraphQLError } from '../error/GraphQLError'; @@ -239,7 +242,10 @@ function validateTypes(context: SchemaValidationContext): void { // Ensure they are named correctly. validateName(context, type); - if (isObjectType(type)) { + if (isScalarType(type)) { + // Ensure ofType is a built-in scalar + validateScalarOfType(context, type); + } else if (isObjectType(type)) { // Ensure fields are valid validateFields(context, type); @@ -261,6 +267,20 @@ function validateTypes(context: SchemaValidationContext): void { }); } +function validateScalarOfType( + context: SchemaValidationContext, + type: GraphQLScalarType, +): void { + const ofType = type.ofType; + if (ofType && !isSpecifiedScalarType(ofType)) { + context.reportError( + `Type ${type.name} may only be described in terms of a built-in scalar ` + + `type. However ${ofType.name} is not a built-in scalar type.`, + type.astNode, + ); + } +} + function validateFields( context: SchemaValidationContext, type: GraphQLObjectType | GraphQLInterfaceType, diff --git a/src/utilities/__tests__/schemaPrinter-test.js b/src/utilities/__tests__/schemaPrinter-test.js index 96a6a21104..6c3392ec9c 100644 --- a/src/utilities/__tests__/schemaPrinter-test.js +++ b/src/utilities/__tests__/schemaPrinter-test.js @@ -498,8 +498,17 @@ describe('Type System Printer', () => { }); it('Custom Scalar', () => { + const EvenType = new GraphQLScalarType({ + name: 'Even', + ofType: GraphQLInt, + serialize(value) { + return value % 2 === 1 ? value : null; + }, + }); + const OddType = new GraphQLScalarType({ name: 'Odd', + // No ofType in this test case. serialize(value) { return value % 2 === 1 ? value : null; }, @@ -508,6 +517,7 @@ describe('Type System Printer', () => { const Root = new GraphQLObjectType({ name: 'Root', fields: { + even: { type: EvenType }, odd: { type: OddType }, }, }); @@ -519,9 +529,12 @@ describe('Type System Printer', () => { query: Root } + scalar Even = Int + scalar Odd type Root { + even: Even odd: Odd } `); @@ -799,7 +812,7 @@ describe('Type System Printer', () => { """An enum describing what kind of type a given \`__Type\` is.""" enum __TypeKind { - """Indicates this type is a scalar.""" + """Indicates this type is a scalar. \`ofType\` is a valid field.""" SCALAR """ @@ -1018,7 +1031,7 @@ describe('Type System Printer', () => { # An enum describing what kind of type a given \`__Type\` is. enum __TypeKind { - # Indicates this type is a scalar. + # Indicates this type is a scalar. \`ofType\` is a valid field. SCALAR # Indicates this type is an object. \`fields\` and \`interfaces\` are valid fields. diff --git a/src/utilities/buildASTSchema.js b/src/utilities/buildASTSchema.js index 16ed9a99f4..27690f6882 100644 --- a/src/utilities/buildASTSchema.js +++ b/src/utilities/buildASTSchema.js @@ -442,6 +442,10 @@ export class ASTDefinitionBuilder { return new GraphQLScalarType({ name: def.name.value, description: getDescription(def, this._options), + // Note: While this could make assertions to get the correctly typed + // value, that would throw immediately while type system validation + // with validateSchema() will produce more actionable results. + ofType: def.type && (this.buildType(def.type): any), astNode: def, serialize: value => value, }); diff --git a/src/utilities/buildClientSchema.js b/src/utilities/buildClientSchema.js index cc883b3a88..a8ee367249 100644 --- a/src/utilities/buildClientSchema.js +++ b/src/utilities/buildClientSchema.js @@ -26,6 +26,7 @@ import { GraphQLEnumType, GraphQLInputObjectType, assertNullableType, + assertScalarType, assertObjectType, assertInterfaceType, } from '../type/definition'; @@ -179,6 +180,11 @@ export function buildClientSchema( return assertInterfaceType(type); } + function getScalarType(typeRef: IntrospectionTypeRef): GraphQLScalarType { + const type = getType(typeRef); + return assertScalarType(type); + } + // Given a type's introspection result, construct the correct // GraphQLType instance. function buildType(type: IntrospectionType): GraphQLNamedType { @@ -208,9 +214,13 @@ export function buildClientSchema( function buildScalarDef( scalarIntrospection: IntrospectionScalarType, ): GraphQLScalarType { + const ofType = scalarIntrospection.ofType + ? getScalarType(scalarIntrospection.ofType) + : undefined; return new GraphQLScalarType({ name: scalarIntrospection.name, description: scalarIntrospection.description, + ofType, serialize: value => value, }); } diff --git a/src/utilities/findBreakingChanges.js b/src/utilities/findBreakingChanges.js index b5787df2d4..61a3ea2185 100644 --- a/src/utilities/findBreakingChanges.js +++ b/src/utilities/findBreakingChanges.js @@ -161,6 +161,18 @@ export function findTypesThatChangedKind( `${typeName} changed from ` + `${typeKindName(oldType)} to ${typeKindName(newType)}.`, }); + } else if (isScalarType(oldType) && isScalarType(newType)) { + const oldOfType = oldType.ofType; + const newOfType = newType.ofType; + if (oldOfType && newOfType && oldOfType !== newOfType) { + breakingChanges.push({ + type: BreakingChangeType.TYPE_CHANGED_KIND, + description: + `${typeName} changed from ` + + `${typeKindName(oldType)} serialized as ${oldOfType.name} ` + + `to ${typeKindName(newType)} serialized as ${newOfType.name}.`, + }); + } } }); return breakingChanges; diff --git a/src/utilities/introspectionQuery.js b/src/utilities/introspectionQuery.js index 4071250998..26ca0867a5 100644 --- a/src/utilities/introspectionQuery.js +++ b/src/utilities/introspectionQuery.js @@ -150,6 +150,7 @@ export type IntrospectionScalarType = { +kind: 'SCALAR', +name: string, +description?: ?string, + +ofType?: ?IntrospectionNamedTypeRef, }; export type IntrospectionObjectType = { diff --git a/src/utilities/schemaPrinter.js b/src/utilities/schemaPrinter.js index 568274e148..472dc7c885 100644 --- a/src/utilities/schemaPrinter.js +++ b/src/utilities/schemaPrinter.js @@ -170,7 +170,8 @@ export function printType(type: GraphQLNamedType, options?: Options): string { } function printScalar(type: GraphQLScalarType, options): string { - return printDescription(options, type) + `scalar ${type.name}`; + const ofType = type.ofType ? ` = ${type.ofType.name}` : ''; + return printDescription(options, type) + `scalar ${type.name}${ofType}`; } function printObject(type: GraphQLObjectType, options): string { From 4f72c1386e266539a520441916c40a23e6bbfba7 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Fri, 16 Jun 2017 16:54:11 -0700 Subject: [PATCH 2/2] Replace `=` with `as` --- .../__tests__/schema-kitchen-sink.graphql | 2 +- src/language/__tests__/schema-printer-test.js | 2 +- src/language/parser.js | 22 +++++++++++++---- src/language/printer.js | 2 +- src/type/__tests__/introspection-test.js | 3 ++- src/type/introspection.js | 7 +++--- src/utilities/__tests__/schemaPrinter-test.js | 24 ++++++++++--------- src/utilities/schemaPrinter.js | 2 +- 8 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/language/__tests__/schema-kitchen-sink.graphql b/src/language/__tests__/schema-kitchen-sink.graphql index 2a9f8344c2..20fe9b48b6 100644 --- a/src/language/__tests__/schema-kitchen-sink.graphql +++ b/src/language/__tests__/schema-kitchen-sink.graphql @@ -65,7 +65,7 @@ extend union Feed @onUnion scalar CustomScalar -scalar StringEncodedCustomScalar = String +scalar StringEncodedCustomScalar as String scalar AnnotatedScalar @onScalar diff --git a/src/language/__tests__/schema-printer-test.js b/src/language/__tests__/schema-printer-test.js index f557d40ef8..b6324e5b1c 100644 --- a/src/language/__tests__/schema-printer-test.js +++ b/src/language/__tests__/schema-printer-test.js @@ -109,7 +109,7 @@ describe('Printer', () => { scalar CustomScalar - scalar StringEncodedCustomScalar = String + scalar StringEncodedCustomScalar as String scalar AnnotatedScalar @onScalar diff --git a/src/language/parser.js b/src/language/parser.js index 4df40d6ce7..a60ba1a2c7 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -891,16 +891,14 @@ function parseOperationTypeDefinition( /** * ScalarTypeDefinition : Description? scalar Name ScalarOfType? Directives[Const]? - * ScalarOfType : = NamedType + * ScalarOfType : as NamedType */ function parseScalarTypeDefinition(lexer: Lexer<*>): ScalarTypeDefinitionNode { const start = lexer.token; const description = parseDescription(lexer); expectKeyword(lexer, 'scalar'); const name = parseName(lexer); - const type = skip(lexer, TokenKind.EQUALS) - ? parseNamedType(lexer) - : undefined; + const type = skipKeyword(lexer, 'as') ? parseNamedType(lexer) : undefined; const directives = parseDirectives(lexer, true); return { kind: SCALAR_TYPE_DEFINITION, @@ -1508,10 +1506,24 @@ function expect(lexer: Lexer<*>, kind: string): Token { } /** - * If the next token is a keyword with the given value, return that token after + * If the next token is a keyword with the given value, return true after * advancing the lexer. Otherwise, do not change the parser state and return * false. */ +function skipKeyword(lexer: Lexer<*>, value: string): boolean { + const token = lexer.token; + const match = token.kind === TokenKind.NAME && token.value === value; + if (match) { + lexer.advance(); + } + return match; +} + +/** + * If the next token is a keyword with the given value, return that token after + * advancing the lexer. Otherwise, do not change the parser state and throw + * an error. + */ function expectKeyword(lexer: Lexer<*>, value: string): Token { const token = lexer.token; if (token.kind === TokenKind.NAME && token.value === value) { diff --git a/src/language/printer.js b/src/language/printer.js index eb18efac9a..a7d94e9f69 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -114,7 +114,7 @@ const printDocASTReducer = { join( [ description, - join(['scalar', name, wrap('= ', type), join(directives, ' ')], ' '), + join(['scalar', name, wrap('as ', type), join(directives, ' ')], ' '), ], '\n', ), diff --git a/src/type/__tests__/introspection-test.js b/src/type/__tests__/introspection-test.js index 2c696d32f2..b1ad489ced 100644 --- a/src/type/__tests__/introspection-test.js +++ b/src/type/__tests__/introspection-test.js @@ -1308,7 +1308,8 @@ describe('Introspection', () => { enumValues: [ { description: - 'Indicates this type is a scalar. `ofType` is a valid field.', + 'Indicates this type is a scalar. ' + + '`ofType` may represent how this scalar is serialized.', name: 'SCALAR', }, { diff --git a/src/type/introspection.js b/src/type/introspection.js index 03df860fe4..907368c01a 100644 --- a/src/type/introspection.js +++ b/src/type/introspection.js @@ -207,8 +207,8 @@ export const __Type = new GraphQLObjectType({ '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. ' + + 'information about that type. Scalar types provide a name, description ' + + 'and how they serialize, while Enum types provide their possible 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.', @@ -382,7 +382,8 @@ export const __TypeKind = new GraphQLEnumType({ SCALAR: { value: TypeKind.SCALAR, description: - 'Indicates this type is a scalar. `ofType` is a valid field.', + 'Indicates this type is a scalar. ' + + '`ofType` may represent how this scalar is serialized.', }, OBJECT: { value: TypeKind.OBJECT, diff --git a/src/utilities/__tests__/schemaPrinter-test.js b/src/utilities/__tests__/schemaPrinter-test.js index 6c3392ec9c..9748251deb 100644 --- a/src/utilities/__tests__/schemaPrinter-test.js +++ b/src/utilities/__tests__/schemaPrinter-test.js @@ -529,7 +529,7 @@ describe('Type System Printer', () => { query: Root } - scalar Even = Int + scalar Even as Int scalar Odd @@ -793,10 +793,10 @@ describe('Type System Printer', () => { types in GraphQL as represented by the \`__TypeKind\` enum. 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. + type. Scalar types provide a name, description and how they serialize, while + Enum types provide their possible 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. """ type __Type { kind: __TypeKind! @@ -812,7 +812,9 @@ describe('Type System Printer', () => { """An enum describing what kind of type a given \`__Type\` is.""" enum __TypeKind { - """Indicates this type is a scalar. \`ofType\` is a valid field.""" + """ + Indicates this type is a scalar. \`ofType\` may represent how this scalar is serialized. + """ SCALAR """ @@ -1013,10 +1015,10 @@ describe('Type System Printer', () => { # types in GraphQL as represented by the \`__TypeKind\` enum. # # 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. + # type. Scalar types provide a name, description and how they serialize, while + # Enum types provide their possible 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. type __Type { kind: __TypeKind! name: String @@ -1031,7 +1033,7 @@ describe('Type System Printer', () => { # An enum describing what kind of type a given \`__Type\` is. enum __TypeKind { - # Indicates this type is a scalar. \`ofType\` is a valid field. + # Indicates this type is a scalar. \`ofType\` may represent how this scalar is serialized. SCALAR # Indicates this type is an object. \`fields\` and \`interfaces\` are valid fields. diff --git a/src/utilities/schemaPrinter.js b/src/utilities/schemaPrinter.js index 472dc7c885..84249933ce 100644 --- a/src/utilities/schemaPrinter.js +++ b/src/utilities/schemaPrinter.js @@ -170,7 +170,7 @@ export function printType(type: GraphQLNamedType, options?: Options): string { } function printScalar(type: GraphQLScalarType, options): string { - const ofType = type.ofType ? ` = ${type.ofType.name}` : ''; + const ofType = type.ofType ? ` as ${type.ofType.name}` : ''; return printDescription(options, type) + `scalar ${type.name}${ofType}`; }