diff --git a/src/language/schema/__tests__/materializer.js b/src/language/schema/__tests__/materializer.js index 4bfd5c3e47b..3057892a69f 100644 --- a/src/language/schema/__tests__/materializer.js +++ b/src/language/schema/__tests__/materializer.js @@ -26,7 +26,7 @@ import { createSchemaFromDSL } from '../'; */ async function cycleOutput(body, queryType) { var schema = await createSchemaFromDSL(body, queryType); - return '\n' + await printSchema(schema) + '\n'; + return '\n' + await printSchema(schema); } describe('Schema Materializer', () => { diff --git a/src/type/__tests__/schemaPrinter.js b/src/type/__tests__/schemaPrinter.js index 485d56f496f..ca8a65c5f8f 100644 --- a/src/type/__tests__/schemaPrinter.js +++ b/src/type/__tests__/schemaPrinter.js @@ -29,16 +29,16 @@ import { // 80+ char lines are useful in describe/it, so ignore in this file. /*eslint-disable max-len */ -async function printForTest(schema) { - return '\n' + await printSchema(schema) + '\n'; +function printForTest(schema) { + return '\n' + printSchema(schema); } -async function printSingleFieldSchema(fieldConfig) { +function printSingleFieldSchema(fieldConfig) { var Root = new GraphQLObjectType({ name: 'Root', fields: { singleField: fieldConfig }, }); - return await printForTest(new GraphQLSchema({query: Root})); + return printForTest(new GraphQLSchema({query: Root})); } function listOf(type) { @@ -50,8 +50,8 @@ function nonNull(type) { } describe('Type System Printer', () => { - it('Prints String Field', async () => { - var output = await printSingleFieldSchema({type: GraphQLString}); + it('Prints String Field', () => { + var output = printSingleFieldSchema({type: GraphQLString}); expect(output).to.equal(` type Root { singleField: String @@ -60,8 +60,8 @@ type Root { ); }); - it('Prints [String] Field', async () => { - var output = await printSingleFieldSchema({type: listOf(GraphQLString)}); + it('Prints [String] Field', () => { + var output = printSingleFieldSchema({type: listOf(GraphQLString)}); expect(output).to.equal(` type Root { singleField: [String] @@ -70,8 +70,8 @@ type Root { ); }); - it('Prints String! Field', async () => { - var output = await printSingleFieldSchema({type: nonNull(GraphQLString)}); + it('Prints String! Field', () => { + var output = printSingleFieldSchema({type: nonNull(GraphQLString)}); expect(output).to.equal(` type Root { singleField: String! @@ -80,8 +80,8 @@ type Root { ); }); - it('Prints [String]! Field', async () => { - var output = await printSingleFieldSchema({type: nonNull(listOf(GraphQLString))}); + it('Prints [String]! Field', () => { + var output = printSingleFieldSchema({type: nonNull(listOf(GraphQLString))}); expect(output).to.equal(` type Root { singleField: [String]! @@ -90,8 +90,8 @@ type Root { ); }); - it('Prints [String!] Field', async () => { - var output = await printSingleFieldSchema({type: listOf(nonNull(GraphQLString))}); + it('Prints [String!] Field', () => { + var output = printSingleFieldSchema({type: listOf(nonNull(GraphQLString))}); expect(output).to.equal(` type Root { singleField: [String!] @@ -100,8 +100,8 @@ type Root { ); }); - it('Prints [String!]! Field', async () => { - var output = await printSingleFieldSchema({type: nonNull(listOf(nonNull(GraphQLString)))}); + it('Prints [String!]! Field', () => { + var output = printSingleFieldSchema({type: nonNull(listOf(nonNull(GraphQLString)))}); expect(output).to.equal(` type Root { singleField: [String!]! @@ -110,7 +110,7 @@ type Root { ); }); - it('Print Object Field', async() => { + it('Print Object Field', () => { var FooType = new GraphQLObjectType({ name: 'Foo', fields: { str: { type: GraphQLString } } @@ -122,7 +122,7 @@ type Root { }); var Schema = new GraphQLSchema({query: Root}); - var output = await printForTest(Schema); + var output = printForTest(Schema); expect(output).to.equal(` type Foo { str: String @@ -135,8 +135,8 @@ type Root { ); }); - it('Prints String Field With Int Arg', async () => { - var output = await printSingleFieldSchema( + it('Prints String Field With Int Arg', () => { + var output = printSingleFieldSchema( { type: GraphQLString, args: { argOne: { type: GraphQLInt } }, @@ -150,8 +150,8 @@ type Root { ); }); - it('Prints String Field With Int Arg With Default', async () => { - var output = await printSingleFieldSchema( + it('Prints String Field With Int Arg With Default', () => { + var output = printSingleFieldSchema( { type: GraphQLString, args: { argOne: { type: GraphQLInt, defaultValue: 2 } }, @@ -165,8 +165,8 @@ type Root { ); }); - it('Prints String Field With Int! Arg', async () => { - var output = await printSingleFieldSchema( + it('Prints String Field With Int! Arg', () => { + var output = printSingleFieldSchema( { type: GraphQLString, args: { argOne: { type: nonNull(GraphQLInt) } }, @@ -180,8 +180,8 @@ type Root { ); }); - it('Prints String Field With Multiple Args', async () => { - var output = await printSingleFieldSchema( + it('Prints String Field With Multiple Args', () => { + var output = printSingleFieldSchema( { type: GraphQLString, args: { @@ -198,8 +198,8 @@ type Root { ); }); - it('Prints String Field With Multiple Args, First is Default', async () => { - var output = await printSingleFieldSchema( + it('Prints String Field With Multiple Args, First is Default', () => { + var output = printSingleFieldSchema( { type: GraphQLString, args: { @@ -217,8 +217,8 @@ type Root { ); }); - it('Prints String Field With Multiple Args, Second is Default', async () => { - var output = await printSingleFieldSchema( + it('Prints String Field With Multiple Args, Second is Default', () => { + var output = printSingleFieldSchema( { type: GraphQLString, args: { @@ -236,8 +236,8 @@ type Root { ); }); - it('Prints String Field With Multiple Args, Last is Default', async () => { - var output = await printSingleFieldSchema( + it('Prints String Field With Multiple Args, Last is Default', () => { + var output = printSingleFieldSchema( { type: GraphQLString, args: { @@ -255,7 +255,7 @@ type Root { ); }); - it('Print Interface', async() => { + it('Print Interface', () => { var FooType = new GraphQLInterfaceType({ name: 'Foo', fields: { str: { type: GraphQLString } }, @@ -273,7 +273,7 @@ type Root { }); var Schema = new GraphQLSchema({query: Root}); - var output = await printForTest(Schema); + var output = printForTest(Schema); expect(output).to.equal(` type Bar implements Foo { str: String @@ -290,7 +290,7 @@ type Root { ); }); - it('Print Multiple Interface', async() => { + it('Print Multiple Interface', () => { var FooType = new GraphQLInterfaceType({ name: 'Foo', fields: { str: { type: GraphQLString } }, @@ -316,7 +316,7 @@ type Root { }); var Schema = new GraphQLSchema({query: Root}); - var output = await printForTest(Schema); + var output = printForTest(Schema); expect(output).to.equal(` interface Baaz { int: Int @@ -338,7 +338,7 @@ type Root { ); }); - it('Print Unions', async() => { + it('Print Unions', () => { var FooType = new GraphQLObjectType({ name: 'Foo', fields: { @@ -372,7 +372,7 @@ type Root { }); var Schema = new GraphQLSchema({query: Root}); - var output = await printForTest(Schema); + var output = printForTest(Schema); expect(output).to.equal(` type Bar { str: String @@ -394,7 +394,7 @@ union SingleUnion = Foo ); }); - it('Print Input Type', async() => { + it('Print Input Type', () => { var InputType = new GraphQLInputObjectType({ name: 'InputType', fields: { @@ -413,7 +413,7 @@ union SingleUnion = Foo }); var Schema = new GraphQLSchema({query: Root}); - var output = await printForTest(Schema); + var output = printForTest(Schema); expect(output).to.equal(` input InputType { int: Int @@ -425,7 +425,7 @@ type Root { `); }); - it('Custom Scalar', async () => { + it('Custom Scalar', () => { var OddType = new GraphQLScalarType({ name: 'Odd', coerce(value) { @@ -441,7 +441,7 @@ type Root { }); var Schema = new GraphQLSchema({query: Root}); - var output = await printForTest(Schema); + var output = printForTest(Schema); expect(output).to.equal(` scalar Odd @@ -452,7 +452,7 @@ type Root { ); }); - it('Enum', async() => { + it('Enum', () => { var RGBType = new GraphQLEnumType({ name: 'RGB', values: { @@ -470,7 +470,7 @@ type Root { }); var Schema = new GraphQLSchema({query: Root}); - var output = await printForTest(Schema); + var output = printForTest(Schema); expect(output).to.equal(` enum RGB { RED @@ -484,13 +484,13 @@ type Root { `); }); - it('Print Introspection Schema', async() => { + it('Print Introspection Schema', () => { var Root = new GraphQLObjectType({ name: 'Root', fields: {}, }); var Schema = new GraphQLSchema({query: Root}); - var output = '\n' + await printIntrospectionSchema(Schema) + '\n'; + var output = '\n' + printIntrospectionSchema(Schema); var introspectionSchema = ` type __Directive { name: String! diff --git a/src/type/schemaPrinter.js b/src/type/schemaPrinter.js index ddadf132c11..860f157b079 100644 --- a/src/type/schemaPrinter.js +++ b/src/type/schemaPrinter.js @@ -8,198 +8,135 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -import { introspectionQuery } from './introspectionQuery'; -import { graphql } from '../'; - -import type { - IntrospectionQuery, - IntrospectionType, - IntrospectionScalarType, - IntrospectionObjectType, - IntrospectionInterfaceType, - IntrospectionUnionType, - IntrospectionEnumType, - IntrospectionInputObjectType, - IntrospectionTypeRef, - IntrospectionListTypeRef, - IntrospectionNonNullTypeRef, - IntrospectionField, - IntrospectionInputValue, - IntrospectionEnumValue, -} from './introspectionQuery'; - +import invariant from '../utils/invariant'; +import isNullish from '../utils/isNullish'; +import astFromValue from '../utils/astFromValue'; +import { print } from '../language/printer'; import type { GraphQLSchema } from './schema'; +import type { GraphQLNamedType } from './definition'; +import { + GraphQLScalarType, + GraphQLObjectType, + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLEnumType, + GraphQLInputObjectType, +} from './definition'; -var TypeKind = { - SCALAR: 'SCALAR', - OBJECT: 'OBJECT', - INTERFACE: 'INTERFACE', - UNION: 'UNION', - ENUM: 'ENUM', - INPUT_OBJECT: 'INPUT_OBJECT', - LIST: 'LIST', - NON_NULL: 'NON_NULL', -}; - -// e.g Int, Int!, [Int!]! -function printTypeDecl(type: IntrospectionTypeRef): string { - if (type.kind === TypeKind.LIST) { - var itemTypeRef = ((type: any): IntrospectionListTypeRef).ofType; - if (!itemTypeRef) { - throw new Error('Decorated type deeper than introspection query.'); - } - return '[' + printTypeDecl(itemTypeRef) + ']'; - } else if (type.kind === TypeKind.NON_NULL) { - var nullableTypeRef = ((type: any): IntrospectionNonNullTypeRef).ofType; - if (!nullableTypeRef) { - throw new Error('Decorated type deeper than introspection query.'); - } - return printTypeDecl(nullableTypeRef) + '!'; - } else { - return type.name; - } -} - -function printField(field: IntrospectionField): string { - return `${field.name}${printArgs(field)}: ${printTypeDecl(field.type)}`; -} - -function printArg(arg: IntrospectionInputValue): string { - var argDecl = `${arg.name}: ${printTypeDecl(arg.type)}`; - if (arg.defaultValue !== null) { - argDecl += ` = ${arg.defaultValue}`; - } - return argDecl; -} - -function printArgs(field: IntrospectionField): string { - if (field.args.length === 0) { - return ''; - } - return '(' + field.args.map(printArg).join(', ') + ')'; -} -function printImplementedInterfaces(type: IntrospectionObjectType) { - if (type.interfaces.length === 0) { - return ''; - } - return ' implements ' + type.interfaces.map(i => i.name).join(', '); +export function printSchema(schema: GraphQLSchema): string { + return printFilteredSchema(schema, isDefinedType); } -function printObject(type: IntrospectionObjectType) { - return `type ${type.name}${printImplementedInterfaces(type)} {` + '\n' + - printFields(type.fields) + '\n' + - '}'; +export function printIntrospectionSchema(schema: GraphQLSchema): string { + return printFilteredSchema(schema, isIntrospectionType); } -function printInterface(type: IntrospectionInterfaceType) { - return `interface ${type.name} {` + '\n' + - printFields(type.fields) + '\n' + - '}'; -} - -function printInputObject(type: IntrospectionInputObjectType) { - return `input ${type.name} {` + '\n' + - printInputFields(type.inputFields) + '\n' + - '}'; +function isDefinedType(typename: string): boolean { + return !isIntrospectionType(typename) && !isBuiltInScalar(typename); } -function printFields(fields: Array) { - return fields.map(f => ' ' + printField(f)).join('\n'); +function isIntrospectionType(typename: string): boolean { + return typename.startsWith('__'); } -function printInputFields(fields: Array) { - return fields.map(f => ' ' + printInputField(f)).join('\n'); +function isBuiltInScalar(typename: string): boolean { + return ( + typename === 'String' || + typename === 'Boolean' || + typename === 'Int' || + typename === 'Float' || + typename === 'ID' + ); } -function printInputField(field: IntrospectionInputValue) { - return `${field.name}: ${printTypeDecl(field.type)}`; -} - -function printUnion(type: IntrospectionUnionType) { - var typeList = type.possibleTypes.map(t => t.name).join(' | '); - return `union ${type.name} = ${typeList}`; +function printFilteredSchema( + schema: GraphQLSchema, + typeFilter: (type: string) => boolean +): string { + var typeMap = schema.getTypeMap(); + var types = Object.keys(typeMap) + .filter(typeFilter) + .sort((name1, name2) => name1.localeCompare(name2)) + .map(typeName => typeMap[typeName]); + return types.map(printType).join('\n\n') + '\n'; +} + +function printType(type: GraphQLNamedType): string { + if (type instanceof GraphQLScalarType) { + return printScalar(type); + } else if (type instanceof GraphQLObjectType) { + return printObject(type); + } else if (type instanceof GraphQLInterfaceType) { + return printInterface(type); + } else if (type instanceof GraphQLUnionType) { + return printUnion(type); + } else if (type instanceof GraphQLEnumType) { + return printEnum(type); + } else { + invariant(type instanceof GraphQLInputObjectType); + return printInputObject(type); + } } -function printScalar(type: IntrospectionScalarType) { +function printScalar(type: GraphQLScalarType): string { return `scalar ${type.name}`; } -function printEnumValues(values: Array) { - return values.map(v => ' ' + v.name).join('\n'); -} - -function printEnum(type: IntrospectionEnumType) { - return `enum ${type.name} { -${printEnumValues(type.enumValues)} -}`; -} - -function printType(type: IntrospectionType) { - switch (type.kind) { - case TypeKind.OBJECT: - return printObject(type); - case TypeKind.UNION: - return printUnion(type); - case TypeKind.INTERFACE: - return printInterface(type); - case TypeKind.INPUT_OBJECT: - return printInputObject(type); - case TypeKind.SCALAR: - return printScalar(type); - case TypeKind.ENUM: - return printEnum(type); - default: - throw new Error('Invalid kind: ' + type.kind); - } -} - -function isBuiltInScalar(type: IntrospectionScalarType): boolean { - return type.name === 'String' || - type.name === 'Boolean' || - type.name === 'Int' || - type.name === 'Float' || - type.name === 'ID'; +function printObject(type: GraphQLObjectType): string { + var interfaces = type.getInterfaces(); + var implementedInterfaces = interfaces.length ? + ' implements ' + interfaces.map(i => i.name).join(', ') : ''; + return `type ${type.name}${implementedInterfaces} {\n` + + printFields(type) + '\n' + + '}'; } -function isIntrospectionType(type: IntrospectionType): boolean { - return type.name.startsWith('__'); +function printInterface(type: GraphQLInterfaceType): string { + return `interface ${type.name} {\n` + + printFields(type) + '\n' + + '}'; } -function isBuiltIn(type: IntrospectionType): boolean { - return isIntrospectionType(type) || isBuiltInScalar(type); +function printUnion(type: GraphQLUnionType): string { + return `union ${type.name} = ${type.getPossibleTypes().join(' | ')}`; } -export async function printSchema(schema: GraphQLSchema): Promise { - return await printFilteredSchema(schema, t => !isBuiltIn(t)); +function printEnum(type: GraphQLEnumType): string { + var valueMap = type.getValues(); + var values = Object.keys(valueMap).map(valueName => valueMap[valueName]); + return `enum ${type.name} {\n` + + values.map(v => ' ' + v.name).join('\n') + '\n' + + '}'; } -export async function printIntrospectionSchema( - schema: GraphQLSchema -): Promise { - return await printFilteredSchema(schema, isIntrospectionType); +function printInputObject(type: GraphQLInputObjectType): string { + var fieldMap = type.getFields(); + var fields = Object.keys(fieldMap).map(fieldName => fieldMap[fieldName]); + return `input ${type.name} {\n` + + fields.map(f => ' ' + printInputValue(f)).join('\n') + '\n' + + '}'; } -export function printSchemaFromResult( - result: IntrospectionQuery -): string { - return printFilteredSchemaFromResult(result, t => !isBuiltIn(t)); +function printFields(type) { + var fieldMap = type.getFields(); + var fields = Object.keys(fieldMap).map(fieldName => fieldMap[fieldName]); + return fields.map( + f => ` ${f.name}${printArgs(f)}: ${f.type}` + ).join('\n'); } -function printFilteredSchemaFromResult( - result: IntrospectionQuery, - typeFilter: (type: IntrospectionType) => boolean -): string { - var schemaResult = result.__schema; - var types = schemaResult.types.filter(typeFilter); - types = types.sort((t1, t2) => t1.name.localeCompare(t2.name)); - return types.map(printType).join('\n\n'); +function printArgs(field) { + if (field.args.length === 0) { + return ''; + } + return '(' + field.args.map(printInputValue).join(', ') + ')'; } -async function printFilteredSchema( - schema: GraphQLSchema, - typeFilter: (type: IntrospectionType) => boolean -): Promise { - var result = await graphql(schema, introspectionQuery); - return printFilteredSchemaFromResult(result.data, typeFilter); +function printInputValue(arg) { + var argDecl = `${arg.name}: ${arg.type}`; + if (!isNullish(arg.defaultValue)) { + argDecl += ` = ${print(astFromValue(arg.defaultValue, arg.type))}`; + } + return argDecl; } diff --git a/src/utils/__tests__/astFromValue.js b/src/utils/__tests__/astFromValue.js new file mode 100644 index 00000000000..fcbb5f61a6b --- /dev/null +++ b/src/utils/__tests__/astFromValue.js @@ -0,0 +1,265 @@ +/** + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +import { describe, it } from 'mocha'; +import { expect } from 'chai'; +import astFromValue from '../astFromValue'; +import { + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLList +} from '../../type/definition'; +import { GraphQLFloat } from '../../type/scalars'; + + +describe('astFromValue', () => { + + it('converts boolean values to ASTs', () => { + expect(astFromValue( + true + )).to.deep.equal({ + kind: 'BooleanValue', + value: true + }); + + expect(astFromValue( + false + )).to.deep.equal({ + kind: 'BooleanValue', + value: false + }); + }); + + it('converts numeric values to ASTs', () => { + expect(astFromValue( + 123 + )).to.deep.equal({ + kind: 'IntValue', + value: '123' + }); + + expect(astFromValue( + 123.0 + )).to.deep.equal({ + kind: 'IntValue', + value: '123' + }); + + expect(astFromValue( + 123.5 + )).to.deep.equal({ + kind: 'FloatValue', + value: '123.5' + }); + + expect(astFromValue( + 1e4 + )).to.deep.equal({ + kind: 'IntValue', + value: '10000' + }); + + expect(astFromValue( + 1e40 + )).to.deep.equal({ + kind: 'FloatValue', + value: '1e+40' + }); + }); + + it('converts numeric values to Float ASTs', () => { + expect(astFromValue( + 123, + GraphQLFloat + )).to.deep.equal({ + kind: 'FloatValue', + value: '123.0' + }); + + expect(astFromValue( + 123.0, + GraphQLFloat + )).to.deep.equal({ + kind: 'FloatValue', + value: '123.0' + }); + + expect(astFromValue( + 123.5, + GraphQLFloat + )).to.deep.equal({ + kind: 'FloatValue', + value: '123.5' + }); + + expect(astFromValue( + 1e4, + GraphQLFloat + )).to.deep.equal({ + kind: 'FloatValue', + value: '10000.0' + }); + + expect(astFromValue( + 1e40, + GraphQLFloat + )).to.deep.equal({ + kind: 'FloatValue', + value: '1e+40' + }); + }); + + it('converts string values to ASTs', () => { + expect(astFromValue( + 'hello' + )).to.deep.equal({ + kind: 'StringValue', + value: 'hello' + }); + + expect(astFromValue( + 'VALUE' + )).to.deep.equal({ + kind: 'StringValue', + value: 'VALUE' + }); + + expect(astFromValue( + 'VA\nLUE' + )).to.deep.equal({ + kind: 'StringValue', + value: 'VA\\nLUE' + }); + + expect(astFromValue( + '123' + )).to.deep.equal({ + kind: 'StringValue', + value: '123' + }); + }); + + var myEnum = new GraphQLEnumType({ + name: 'MyEnum', + values: { + HELLO: {}, + GOODBYE: {}, + } + }); + + it('converts string values to Enum ASTs if possible', () => { + expect(astFromValue( + 'hello', + myEnum + )).to.deep.equal({ + kind: 'EnumValue', + value: 'hello' + }); + + expect(astFromValue( + 'HELLO', + myEnum + )).to.deep.equal({ + kind: 'EnumValue', + value: 'HELLO' + }); + + expect(astFromValue( + 'VALUE', + myEnum + )).to.deep.equal({ + kind: 'EnumValue', + value: 'VALUE' + }); + + expect(astFromValue( + 'VA\nLUE', + myEnum + )).to.deep.equal({ + kind: 'StringValue', + value: 'VA\\nLUE' + }); + + expect(astFromValue( + '123', + myEnum + )).to.deep.equal({ + kind: 'StringValue', + value: '123' + }); + }); + + it('converts array values to List ASTs', () => { + expect(astFromValue( + ['FOO', 'BAR'] + )).to.deep.equal({ + kind: 'ListValue', + values: [ + { kind: 'StringValue', value: 'FOO' }, + { kind: 'StringValue', value: 'BAR' }, + ] + }); + + expect(astFromValue( + ['FOO', 'BAR'], + new GraphQLList(myEnum) + )).to.deep.equal({ + kind: 'ListValue', + values: [ + { kind: 'EnumValue', value: 'FOO' }, + { kind: 'EnumValue', value: 'BAR' }, + ] + }); + }); + + it('converts list singletons', () => { + expect(astFromValue( + 'FOO', + new GraphQLList(myEnum) + )).to.deep.equal({ + kind: 'EnumValue', + value: 'FOO' + }); + }); + + it('converts input objects', () => { + expect(astFromValue( + { foo: 3, bar: 'HELLO' } + )).to.deep.equal({ + kind: 'ObjectValue', + fields: [ + { kind: 'ObjectField', name: 'foo', value: + { kind: 'IntValue', value: '3' } }, + { kind: 'ObjectField', name: 'bar', value: + { kind: 'StringValue', value: 'HELLO' } }, + ] + }); + + var inputObj = new GraphQLInputObjectType({ + name: 'MyInputObj', + fields: { + foo: { type: GraphQLFloat }, + bar: { type: myEnum } + } + }); + + expect(astFromValue( + { foo: 3, bar: 'HELLO' }, + inputObj + )).to.deep.equal({ + kind: 'ObjectValue', + fields: [ + { kind: 'ObjectField', name: 'foo', value: + { kind: 'FloatValue', value: '3.0' } }, + { kind: 'ObjectField', name: 'bar', value: + { kind: 'EnumValue', value: 'HELLO' } }, + ] + }); + }); +}); diff --git a/src/utils/astFromValue.js b/src/utils/astFromValue.js new file mode 100644 index 00000000000..ff7696001ef --- /dev/null +++ b/src/utils/astFromValue.js @@ -0,0 +1,129 @@ +/* @flow */ +/** + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +import invariant from '../utils/invariant'; +import isNullish from '../utils/isNullish'; +import type { Value } from '../language/ast'; +import { + INT, + FLOAT, + STRING, + BOOLEAN, + ENUM, + LIST, + OBJECT, + OBJECT_FIELD, +} from '../language/kinds'; +import { + GraphQLType, + GraphQLEnumType, + GraphQLInputObjectType, + GraphQLList, + GraphQLNonNull, +} from '../type/definition'; +import { GraphQLFloat } from '../type/scalars'; + + +/** + * Produces a GraphQL Value AST given a JavaScript value. + * + * Optionally, a GraphQL type may be provided, which will be used to + * disambiguate between value primitives. + * + * | JSON Value | GraphQL Value | + * | ------------- | -------------------- | + * | Object | Input Object | + * | Array | List | + * | Boolean | Boolean | + * | String | String / Enum Value | + * | Number | Int / Float | + * + */ +export default function astFromValue( + value: any, + type?: ?GraphQLType +): ?Value { + if (type instanceof GraphQLNonNull) { + // Note: we're not checking that the result is non-null. + // This function is not responsible for validating the input value. + return astFromValue(value, type.ofType); + } + + if (isNullish(value)) { + return null; + } + + // Convert JavaScript array to GraphQL list. If the GraphQLType is a list, but + // the value is not an array, convert the value using the list's item type. + if (Array.isArray(value)) { + var itemType = type instanceof GraphQLList ? type.ofType : null; + return { + kind: LIST, + values: value.map(item => astFromValue(item, itemType)) + }; + } else if (type instanceof GraphQLList) { + return astFromValue(value, (type: any).ofType); + } + + if (typeof value === 'boolean') { + return { kind: BOOLEAN, value }; + } + + // JavaScript numbers can be Float or Int values. Use the GraphQLType to + // differentiate if available, otherwise prefer Int if the value is a + // valid Int. + if (typeof value === 'number') { + var stringNum = String(value); + var isIntValue = /^[0-9]+$/.test(stringNum); + if (isIntValue) { + if (type === GraphQLFloat) { + return { kind: FLOAT, value: stringNum + '.0' }; + } else { + return { kind: INT, value: stringNum }; + } + } + return { kind: FLOAT, value: stringNum }; + } + + // JavaScript strings can be Enum values or String values. Use the + // GraphQLType to differentiate if possible. + if (typeof value === 'string') { + if (type instanceof GraphQLEnumType && + /^[_a-zA-Z][_a-zA-Z0-9]*$/.test(value)) { + return { kind: ENUM, value }; + } + // Use JSON stringify, which uses the same string encoding as GraphQL, + // then remove the quotes. + return { kind: STRING, value: JSON.stringify(value).slice(1, -1) }; + } + + // last remaining possible typeof + invariant(typeof value === 'object'); + + // Populate the fields of the input object by creating ASTs from each value + // in the JavaScript object. + var fields = []; + Object.keys(value).forEach(fieldName => { + var fieldType; + if (type instanceof GraphQLInputObjectType) { + var fieldDef = type.getFields()[fieldName]; + fieldType = fieldDef && fieldDef.type; + } + var fieldValue = astFromValue(value[fieldName], fieldType); + if (fieldValue) { + fields.push({ + kind: OBJECT_FIELD, + name: fieldName, + value: fieldValue + }); + } + }); + return { kind: OBJECT, fields }; +}